Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c07c896
add 3r project py
jintj Aug 30, 2024
138ae97
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 30, 2024
ef0efaa
update
jintj Sep 20, 2025
0148d86
update
jintj Sep 20, 2025
20f777e
update
jintj Sep 20, 2025
0e87d69
Apply pre-commit auto fixes
pre-commit-ci[bot] Sep 20, 2025
126ecd8
update button tes
jintj Sep 24, 2025
36eee8f
Apply pre-commit auto fixes
pre-commit-ci[bot] Sep 24, 2025
2cc011f
update
jintj Sep 24, 2025
b4cdbf6
Merge branch 'master' of https://github.com/3reality-support/zha-devi…
jintj Sep 24, 2025
89e80cb
Apply pre-commit auto fixes
pre-commit-ci[bot] Sep 24, 2025
115e9e4
Update test_third_button.py
3reality-support Sep 24, 2025
b484bc7
Apply pre-commit auto fixes
pre-commit-ci[bot] Sep 24, 2025
c798a96
Update test_third_button.py
3reality-support Sep 25, 2025
88c9847
Apply pre-commit auto fixes
pre-commit-ci[bot] Sep 25, 2025
4f74b2e
Update test_third_button.py
3reality-support Sep 25, 2025
e25a838
Apply pre-commit auto fixes
pre-commit-ci[bot] Sep 25, 2025
e275a43
Update test_third_button.py
3reality-support Sep 25, 2025
c5b8f9a
Apply pre-commit auto fixes
pre-commit-ci[bot] Sep 25, 2025
86ac6c4
Update test_third_button.py
3reality-support Sep 25, 2025
44748cb
Apply pre-commit auto fixes
pre-commit-ci[bot] Sep 25, 2025
4a8b815
Update test_third_button.py
3reality-support Sep 25, 2025
58b6ed2
Update test_third_button.py
3reality-support Sep 25, 2025
b95f82c
Apply pre-commit auto fixes
pre-commit-ci[bot] Sep 25, 2025
9f0034f
Update test_third_button.py
3reality-support Sep 25, 2025
cfd1145
Apply pre-commit auto fixes
pre-commit-ci[bot] Sep 25, 2025
63a1b15
Update test_third_button.py
3reality-support Sep 25, 2025
60447b7
update
jintj Sep 25, 2025
26a4356
Merge branch 'dev' into master
3reality-support Sep 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions tests/test_third_button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Test the module for the "third button" function to verify the ZHA event capture logic."""

import pytest

import zhaquirks
from zhaquirks.thirdreality.button_v2 import MultistateInputCluster


class MockListener:
"""Simulate listener class for capturing ZHA events."""

def __init__(self):
"""Initialize listener with empty event list."""
self.zha_send_events = []

def zha_send_event(self, action, event_args):
"""Record ZHA events.

Args:
action (str): The type of action for the event.
event_args (dict): Relevant parameters of the event.

"""
self.zha_send_events.append((action, event_args))


zhaquirks.setup()


@pytest.mark.parametrize(
"manufacturer, model",
[("Third Reality, Inc", "3RSB22BZ")],
)
async def test_third_reality_button_v2(zigpy_device_from_v2_quirk, manufacturer, model):
"""Test Third Reality button event conversion and triggering functionality."""
# Create mock device based on the v2 quirk
device = zigpy_device_from_v2_quirk(manufacturer, model)

# Find the MultistateInputCluster
multistate_cluster = next(
(
cluster
for cluster in device.endpoints[1].in_clusters.values()
if isinstance(cluster, MultistateInputCluster)
),
None,
)
assert multistate_cluster is not None, "MultistateInputCluster not found"

# Create mock listener and register it with the cluster
mock_listener = MockListener()
multistate_cluster.add_listener(mock_listener)

# Test 1: Verify single click event conversion
mock_listener.zha_send_events.clear()
multistate_cluster.update_attribute(0x0055, 1) # 1 corresponds to single click
assert len(mock_listener.zha_send_events) == 1
assert mock_listener.zha_send_events[0][0] == "single"

# Test 2: Verify double click event conversion
mock_listener.zha_send_events.clear()
multistate_cluster.update_attribute(0x0055, 2) # 2 corresponds to double click
assert len(mock_listener.zha_send_events) == 1
assert mock_listener.zha_send_events[0][0] == "double"

# Test 3: Verify hold event conversion
mock_listener.zha_send_events.clear()
multistate_cluster.update_attribute(0x0055, 0) # 0 corresponds to hold
assert len(mock_listener.zha_send_events) == 1
assert mock_listener.zha_send_events[0][0] == "hold"

# Test 4: Verify release event conversion
mock_listener.zha_send_events.clear()
multistate_cluster.update_attribute(0x0055, 255) # 255 corresponds to release
assert len(mock_listener.zha_send_events) == 1
assert mock_listener.zha_send_events[0][0] == "release"
92 changes: 92 additions & 0 deletions zhaquirks/thirdreality/button_v2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""Third Reality button devices."""

from typing import Final

from zigpy.quirks import CustomCluster
from zigpy.quirks.v2 import QuirkBuilder
import zigpy.types as t
from zigpy.zcl.clusters.general import MultistateInput
from zigpy.zcl.foundation import BaseAttributeDefs, ZCLAttributeDef

from zhaquirks.const import (
COMMAND,
COMMAND_DOUBLE,
COMMAND_HOLD,
COMMAND_RELEASE,
COMMAND_SINGLE,
DOUBLE_PRESS,
LONG_PRESS,
LONG_RELEASE,
SHORT_PRESS,
VALUE,
ZHA_SEND_EVENT,
)

MOVEMENT_TYPE = {
0: COMMAND_HOLD,
1: COMMAND_SINGLE,
2: COMMAND_DOUBLE,
255: COMMAND_RELEASE,
}


class MultistateInputCluster(CustomCluster, MultistateInput):
"""Multistate input cluster."""

def __init__(self, *args, **kwargs):
"""Init."""
self._current_state = {}
super().__init__(*args, **kwargs)

def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if attrid == 0x0055:
self._current_state[0x0055] = action = MOVEMENT_TYPE.get(value)
event_args = {VALUE: value}
if action is not None:
self.listener_event(ZHA_SEND_EVENT, action, event_args)

# show something in the sensor in HA
super()._update_attribute(0, action)


class ThirdRealityButtonCluster(CustomCluster):
"""Third Reality's button private cluster."""

cluster_id = 0xFF01

class AttributeDefs(BaseAttributeDefs):
"""Define the attributes of a private cluster."""

# cancel double click
cancel_bouble_click: Final = ZCLAttributeDef(
id=0x0000,
type=t.uint8_t,
is_manufacturer_specific=True,
)


(
QuirkBuilder("Third Reality, Inc", "3RSB22BZ")
.replaces(ThirdRealityButtonCluster)
.replaces(MultistateInputCluster)
.number(
attribute_name=ThirdRealityButtonCluster.AttributeDefs.cancel_bouble_click.name,
cluster_id=ThirdRealityButtonCluster.cluster_id,
endpoint_id=1,
min_value=0,
max_value=65535,
step=1,
translation_key="cancel_bouble_click",
fallback_name="Cancel double click",
)
.device_automation_triggers(
{
(DOUBLE_PRESS, DOUBLE_PRESS): {COMMAND: COMMAND_DOUBLE},
(SHORT_PRESS, SHORT_PRESS): {COMMAND: COMMAND_SINGLE},
(LONG_PRESS, LONG_PRESS): {COMMAND: COMMAND_HOLD},
(LONG_RELEASE, LONG_RELEASE): {COMMAND: COMMAND_RELEASE},
}
)
.add_to_registry()
)
Loading