Skip to content

Commit

Permalink
Clean up cluster handler attribute_updated handling (#130)
Browse files Browse the repository at this point in the history
* Clean up cluster handler attribute_updated handling

* add test for smartthings multi

* name and type arg
  • Loading branch information
dmulcahey authored Aug 17, 2024
1 parent 9c55f43 commit c7c921b
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 123 deletions.
65 changes: 62 additions & 3 deletions tests/test_binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
"""Test zhaws binary sensor."""

from collections.abc import Awaitable, Callable
from unittest.mock import call
from unittest.mock import MagicMock, call

import pytest
from zigpy.device import Device as ZigpyDevice
import zigpy.profiles.zha
from zigpy.zcl.clusters import general, measurement, security

from tests.common import find_entity, send_attributes_report, update_attribute_cache
from tests.common import (
find_entity,
get_entity,
send_attributes_report,
update_attribute_cache,
)
from tests.conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
from zha.application import Platform
from zha.application.gateway import Gateway
from zha.application.platforms import PlatformEntity
from zha.application.platforms.binary_sensor import IASZone, Occupancy
from zha.application.platforms.binary_sensor import Accelerometer, IASZone, Occupancy
from zha.zigbee.cluster_handlers.const import SMARTTHINGS_ACCELERATION_CLUSTER
from zha.zigbee.device import Device

DEVICE_IAS = {
Expand All @@ -36,6 +42,24 @@
}


DEVICE_SMARTTHINGS_MULTI = {
1: {
SIG_EP_PROFILE: zigpy.profiles.zha.PROFILE_ID,
SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.IAS_ZONE,
SIG_EP_INPUT: [
general.Basic.cluster_id,
general.PowerConfiguration.cluster_id,
general.Identify.cluster_id,
general.PollControl.cluster_id,
measurement.TemperatureMeasurement.cluster_id,
security.IasZone.cluster_id,
SMARTTHINGS_ACCELERATION_CLUSTER,
],
SIG_EP_OUTPUT: [general.Identify.cluster_id, general.Ota.cluster_id],
}
}


async def async_test_binary_sensor_occupancy(
zha_gateway: Gateway,
cluster: general.OnOff,
Expand Down Expand Up @@ -143,3 +167,38 @@ async def test_binary_sensor(
# test getting messages that trigger and reset the sensors
cluster = getattr(zigpy_device.endpoints[1], cluster_name)
await on_off_test(zha_gateway, cluster, entity, plugs)


async def test_smarttthings_multi(
zigpy_device_mock: Callable[..., ZigpyDevice],
device_joined: Callable[[ZigpyDevice], Awaitable[Device]],
zha_gateway: Gateway,
) -> None:
"""Test smartthings multi."""
zigpy_device = zigpy_device_mock(
DEVICE_SMARTTHINGS_MULTI, manufacturer="Samjin", model="multi"
)
zha_device = await device_joined(zigpy_device)

entity: PlatformEntity = get_entity(
zha_device, Platform.BINARY_SENSOR, entity_type=Accelerometer
)
assert entity is not None
assert isinstance(entity, Accelerometer)
assert entity.PLATFORM == Platform.BINARY_SENSOR
assert entity.is_on is False

st_ch = zha_device.endpoints[1].all_cluster_handlers["1:0xfc02"]
assert st_ch is not None

st_ch.emit_zha_event = MagicMock(wraps=st_ch.emit_zha_event)

await send_attributes_report(zha_gateway, st_ch.cluster, {0x0012: 120})

assert st_ch.emit_zha_event.call_count == 1
assert st_ch.emit_zha_event.mock_calls == [
call(
"attribute_updated",
{"attribute_id": 18, "attribute_name": "x_axis", "attribute_value": 120},
)
]
20 changes: 0 additions & 20 deletions zha/zigbee/cluster_handlers/closures.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

from __future__ import annotations

from typing import Any

import zigpy.types as t
from zigpy.zcl.clusters.closures import ConfigStatus, DoorLock, Shade, WindowCovering

Expand Down Expand Up @@ -70,24 +68,6 @@ def cluster_command(self, tsn, command_id, args):
},
)

def attribute_updated(self, attrid: int, value: Any, _: Any) -> None:
"""Handle attribute update from lock cluster."""
attr_name = self._get_attribute_name(attrid)
self.debug(
"Attribute report '%s'[%s] = %s", self.cluster.name, attr_name, value
)
if attr_name == self._value_attribute:
self.emit(
CLUSTER_HANDLER_ATTRIBUTE_UPDATED,
ClusterAttributeUpdatedEvent(
attribute_id=attrid,
attribute_name=attr_name,
attribute_value=value,
cluster_handler_unique_id=self.unique_id,
cluster_id=self.cluster.cluster_id,
),
)

async def async_set_user_code(self, code_slot: int, user_code: str) -> None:
"""Set the user code for the code slot."""

Expand Down
21 changes: 4 additions & 17 deletions zha/zigbee/cluster_handlers/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import asyncio
from collections.abc import Coroutine
from dataclasses import dataclass
from datetime import datetime
from typing import TYPE_CHECKING, Any, Final

from zhaquirks.quirk_ids import TUYA_PLUG_ONOFF
Expand Down Expand Up @@ -47,13 +48,11 @@
from zha.zigbee.cluster_handlers import (
AttrReportConfig,
ClientClusterHandler,
ClusterAttributeUpdatedEvent,
ClusterHandler,
parse_and_log_command,
registries,
)
from zha.zigbee.cluster_handlers.const import (
CLUSTER_HANDLER_ATTRIBUTE_UPDATED,
CLUSTER_HANDLER_LEVEL_CHANGED,
REPORT_CONFIG_ASAP,
REPORT_CONFIG_BATTERY_SAVE,
Expand Down Expand Up @@ -362,11 +361,13 @@ def cluster_command(self, tsn, command_id, args):
SIGNAL_MOVE_LEVEL, -args[1] if args[0] else args[1]
)

def attribute_updated(self, attrid: int, value: Any, _: Any) -> None:
def attribute_updated(self, attrid: int, value: Any, timestamp: datetime) -> None:
"""Handle attribute updates on this cluster."""
self.debug("received attribute: %s update with value: %s", attrid, value)
if attrid == self.CURRENT_LEVEL:
self.dispatch_level_change(SIGNAL_SET_LEVEL, value)
else:
super().attribute_updated(attrid, value, timestamp)

def dispatch_level_change(self, command, level):
"""Dispatch level change."""
Expand Down Expand Up @@ -513,20 +514,6 @@ def set_to_off(self, *_):
self._off_listener = None
self.cluster.update_attribute(OnOff.AttributeDefs.on_off.id, t.Bool.false)

def attribute_updated(self, attrid: int, value: Any, _: Any) -> None:
"""Handle attribute updates on this cluster."""
if attrid == OnOff.AttributeDefs.on_off.id:
self.emit(
CLUSTER_HANDLER_ATTRIBUTE_UPDATED,
ClusterAttributeUpdatedEvent(
attribute_id=attrid,
attribute_name=OnOff.AttributeDefs.on_off.name,
attribute_value=value,
cluster_handler_unique_id=self.unique_id,
cluster_id=self.cluster.cluster_id,
),
)

async def async_update(self):
"""Initialize cluster handler."""
if self.cluster.is_client:
Expand Down
45 changes: 1 addition & 44 deletions zha/zigbee/cluster_handlers/hvac.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

from __future__ import annotations

from typing import Any

from zigpy.zcl.clusters.hvac import (
Dehumidification,
Fan,
Expand All @@ -12,14 +10,8 @@
UserInterface,
)

from zha.zigbee.cluster_handlers import (
AttrReportConfig,
ClusterAttributeUpdatedEvent,
ClusterHandler,
registries,
)
from zha.zigbee.cluster_handlers import AttrReportConfig, ClusterHandler, registries
from zha.zigbee.cluster_handlers.const import (
CLUSTER_HANDLER_ATTRIBUTE_UPDATED,
REPORT_CONFIG_MAX_INT,
REPORT_CONFIG_MIN_INT,
REPORT_CONFIG_OP,
Expand Down Expand Up @@ -66,24 +58,6 @@ async def async_update(self) -> None:
Fan.AttributeDefs.fan_mode.name, from_cache=False
)

def attribute_updated(self, attrid: int, value: Any, _: Any) -> None:
"""Handle attribute update from fan cluster."""
attr_name = self._get_attribute_name(attrid)
self.debug(
"Attribute report '%s'[%s] = %s", self.cluster.name, attr_name, value
)
if attr_name == "fan_mode":
self.emit(
CLUSTER_HANDLER_ATTRIBUTE_UPDATED,
ClusterAttributeUpdatedEvent(
attribute_id=attrid,
attribute_name=attr_name,
attribute_value=value,
cluster_handler_unique_id=self.unique_id,
cluster_id=self.cluster.cluster_id,
),
)


@registries.CLUSTER_HANDLER_REGISTRY.register(Pump.cluster_id)
class PumpClusterHandler(ClusterHandler):
Expand Down Expand Up @@ -288,23 +262,6 @@ def unoccupied_heating_setpoint(self) -> int | None:
Thermostat.AttributeDefs.unoccupied_heating_setpoint.name
)

def attribute_updated(self, attrid: int, value: Any, _: Any) -> None:
"""Handle attribute update cluster."""
attr_name = self._get_attribute_name(attrid)
self.debug(
"Attribute report '%s'[%s] = %s", self.cluster.name, attr_name, value
)
self.emit(
CLUSTER_HANDLER_ATTRIBUTE_UPDATED,
ClusterAttributeUpdatedEvent(
attribute_id=attrid,
attribute_name=attr_name,
attribute_value=value,
cluster_handler_unique_id=self.unique_id,
cluster_id=self.cluster.cluster_id,
),
)

async def async_set_operation_mode(self, mode) -> bool:
"""Set Operation mode."""
await self.write_attributes_safe(
Expand Down
16 changes: 1 addition & 15 deletions zha/zigbee/cluster_handlers/manufacturerspecific.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from zha.zigbee.cluster_handlers import (
AttrReportConfig,
ClientClusterHandler,
ClusterAttributeUpdatedEvent,
ClusterHandler,
registries,
)
Expand All @@ -28,7 +27,6 @@
ATTRIBUTE_ID,
ATTRIBUTE_NAME,
ATTRIBUTE_VALUE,
CLUSTER_HANDLER_ATTRIBUTE_UPDATED,
IKEA_AIR_PURIFIER_CLUSTER,
IKEA_REMOTE_CLUSTER,
INOVELLI_CLUSTER,
Expand Down Expand Up @@ -220,24 +218,12 @@ def matches(cls, cluster: zigpy.zcl.Cluster, endpoint: Endpoint) -> bool:

def attribute_updated(self, attrid: int, value: Any, _: Any) -> None:
"""Handle attribute updates on this cluster."""
super().attribute_updated(attrid, value, _)
try:
attr_name = self._cluster.attributes[attrid].name
except KeyError:
attr_name = UNKNOWN

if attr_name == self.value_attribute:
self.emit(
CLUSTER_HANDLER_ATTRIBUTE_UPDATED,
ClusterAttributeUpdatedEvent(
attribute_id=attrid,
attribute_name=attr_name,
attribute_value=value,
cluster_handler_unique_id=self.unique_id,
cluster_id=self.cluster.cluster_id,
),
)
return

self.emit_zha_event(
SIGNAL_ATTR_UPDATED,
{
Expand Down
26 changes: 2 additions & 24 deletions zha/zigbee/cluster_handlers/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,8 @@
)

from zha.exceptions import ZHAException
from zha.zigbee.cluster_handlers import (
ClusterAttributeUpdatedEvent,
ClusterHandler,
ClusterHandlerStatus,
registries,
)
from zha.zigbee.cluster_handlers.const import (
CLUSTER_HANDLER_ATTRIBUTE_UPDATED,
CLUSTER_HANDLER_STATE_CHANGED,
)
from zha.zigbee.cluster_handlers import ClusterHandler, ClusterHandlerStatus, registries
from zha.zigbee.cluster_handlers.const import CLUSTER_HANDLER_STATE_CHANGED

if TYPE_CHECKING:
from zha.zigbee.endpoint import Endpoint
Expand Down Expand Up @@ -402,20 +394,6 @@ async def async_configure(self):
self._status = ClusterHandlerStatus.CONFIGURED
self.debug("finished IASZoneClusterHandler configuration")

def attribute_updated(self, attrid: int, value: Any, _: Any) -> None:
"""Handle attribute updates on this cluster."""
if attrid == IasZone.AttributeDefs.zone_status.id:
self.emit(
CLUSTER_HANDLER_ATTRIBUTE_UPDATED,
ClusterAttributeUpdatedEvent(
attribute_id=attrid,
attribute_name=IasZone.AttributeDefs.zone_status.name,
attribute_value=value,
cluster_handler_unique_id=self.unique_id,
cluster_id=self.cluster.cluster_id,
),
)

async def async_update(self) -> None:
"""Retrieve latest state."""
await self.get_attribute_value(
Expand Down

0 comments on commit c7c921b

Please sign in to comment.