Skip to content

Commit

Permalink
clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
dmulcahey committed Mar 26, 2024
1 parent 5c5b6bf commit b984e08
Show file tree
Hide file tree
Showing 15 changed files with 147 additions and 60 deletions.
61 changes: 43 additions & 18 deletions tests/test_binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Test zhaws binary sensor."""

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

import pytest
from zigpy.device import Device as ZigpyDevice
Expand Down Expand Up @@ -36,8 +37,11 @@
}


async def async_test_binary_sensor_on_off(
zha_gateway: Gateway, cluster: general.OnOff, entity: Occupancy
async def async_test_binary_sensor_occupancy(
zha_gateway: Gateway,
cluster: general.OnOff,
entity: Occupancy,
plugs: dict[str, int],
) -> None:
"""Test getting on and off messages for binary sensors."""
# binary sensor on
Expand All @@ -46,11 +50,27 @@ async def async_test_binary_sensor_on_off(

# binary sensor off
await send_attributes_report(zha_gateway, cluster, {1: 1, 0: 0, 2: 2})
assert not entity.is_on
assert entity.is_on is False

# test refresh
cluster.read_attributes.reset_mock() # type: ignore[unreachable]
assert entity.is_on is False
cluster.PLUGGED_ATTR_READS = plugs
update_attribute_cache(cluster)
await entity.async_update()
await zha_gateway.async_block_till_done()
assert cluster.read_attributes.await_count == 1
assert cluster.read_attributes.await_args == call(
["occupancy"], allow_cache=True, only_cache=True, manufacturer=None
)
assert entity.is_on


async def async_test_iaszone_on_off(
zha_gateway: Gateway, cluster: security.IasZone, entity: IASZone
zha_gateway: Gateway,
cluster: security.IasZone,
entity: IASZone,
plugs: dict[str, int],
) -> None:
"""Test getting on and off messages for iaszone binary sensors."""
# binary sensor on
Expand All @@ -61,12 +81,25 @@ async def async_test_iaszone_on_off(
# binary sensor off
cluster.listener_event("cluster_command", 1, 0, [0])
await zha_gateway.async_block_till_done()
assert not entity.is_on
assert entity.is_on is False

# check that binary sensor remains off when non-alarm bits change
cluster.listener_event("cluster_command", 1, 0, [0b1111111100]) # type: ignore[unreachable]
await zha_gateway.async_block_till_done()
assert not entity.is_on
assert entity.is_on is False

# test refresh
cluster.read_attributes.reset_mock()
assert entity.is_on is False
cluster.PLUGGED_ATTR_READS = plugs
update_attribute_cache(cluster)
await entity.async_update()
await zha_gateway.async_block_till_done()
assert cluster.read_attributes.await_count == 1
assert cluster.read_attributes.await_args == call(
["zone_status"], allow_cache=False, only_cache=False, manufacturer=None
)
assert entity.is_on


@pytest.mark.parametrize(
Expand All @@ -81,7 +114,7 @@ async def async_test_iaszone_on_off(
),
(
DEVICE_OCCUPANCY,
async_test_binary_sensor_on_off,
async_test_binary_sensor_occupancy,
"occupancy",
Occupancy,
{"occupancy": 1},
Expand All @@ -102,20 +135,12 @@ async def test_binary_sensor(
zigpy_device = zigpy_device_mock(device)
zha_device = await device_joined(zigpy_device)

entity: PlatformEntity = find_entity(zha_device, Platform.BINARY_SENSOR) # type: ignore
entity: PlatformEntity = find_entity(zha_device, Platform.BINARY_SENSOR)
assert entity is not None
assert isinstance(entity, entity_type)
assert entity.PLATFORM == Platform.BINARY_SENSOR
assert not entity.is_on
assert entity.is_on is False

# 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)

# test refresh
assert not entity.is_on
cluster.PLUGGED_ATTR_READS = plugs
update_attribute_cache(cluster)
await entity.async_update()
await zha_gateway.async_block_till_done()
assert entity.is_on
await on_off_test(zha_gateway, cluster, entity, plugs)
50 changes: 50 additions & 0 deletions tests/test_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,18 @@ async def test_number(
)
assert entity.get_state()["state"] == 30.0

# test updating entity state from client
cluster.read_attributes.reset_mock()
assert entity.get_state()["state"] == 30.0
cluster.PLUGGED_ATTR_READS = {"present_value": 20}
await entity.async_update()
await zha_gateway.async_block_till_done()
assert cluster.read_attributes.await_count == 1
assert cluster.read_attributes.await_args == call(
["present_value"], allow_cache=False, only_cache=False, manufacturer=None
)
assert entity.get_state()["state"] == 20.0


def get_entity(zha_dev: Device, entity_id: str) -> PlatformEntity:
"""Get entity."""
Expand Down Expand Up @@ -249,6 +261,25 @@ async def test_level_control_number(
]
assert entity.get_state()["state"] == initial_value

# test updating entity state from client
level_control_cluster.read_attributes.reset_mock()
assert entity.get_state()["state"] == initial_value
level_control_cluster.PLUGGED_ATTR_READS = {attr: new_value}
await entity.async_update()
await zha_gateway.async_block_till_done()
assert level_control_cluster.read_attributes.await_count == 1
assert level_control_cluster.read_attributes.mock_calls == [
call(
[
attr,
],
allow_cache=False,
only_cache=False,
manufacturer=None,
),
]
assert entity.get_state()["state"] == new_value


@pytest.mark.parametrize(
("attr", "initial_value", "new_value"),
Expand Down Expand Up @@ -335,3 +366,22 @@ async def test_color_number(
call({attr: new_value}, manufacturer=None),
]
assert entity.get_state()["state"] == initial_value

# test updating entity state from client
color_cluster.read_attributes.reset_mock()
assert entity.get_state()["state"] == initial_value
color_cluster.PLUGGED_ATTR_READS = {attr: new_value}
await entity.async_update()
await zha_gateway.async_block_till_done()
assert color_cluster.read_attributes.await_count == 1
assert color_cluster.read_attributes.mock_calls == [
call(
[
attr,
],
allow_cache=False,
only_cache=False,
manufacturer=None,
),
]
assert entity.get_state()["state"] == new_value
28 changes: 14 additions & 14 deletions tests/test_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ def zigpy_device(zigpy_device_mock: Callable[..., ZigpyDevice]) -> ZigpyDevice:
SIG_EP_PROFILE: zha.PROFILE_ID,
}
}
return zigpy_device_mock(endpoints)
zigpy_dev: ZigpyDevice = zigpy_device_mock(endpoints)
# this one is mains powered
zigpy_dev.node_desc.mac_capability_flags |= 0b_0000_0100
return zigpy_dev


@pytest.fixture
Expand Down Expand Up @@ -230,10 +233,12 @@ async def test_switch(
cluster.read_attributes.reset_mock()
assert bool(entity.get_state()["state"]) is False
cluster.PLUGGED_ATTR_READS = {"on_off": True}
update_attribute_cache(cluster)
await entity.async_update()
await zha_gateway.async_block_till_done()
assert cluster.read_attributes.call_count == 1
assert cluster.read_attributes.await_count == 1
assert cluster.read_attributes.await_args == call(
["on_off"], allow_cache=False, only_cache=False, manufacturer=None
)
assert bool(entity.get_state()["state"]) is True


Expand Down Expand Up @@ -393,7 +398,7 @@ def __init__(self, *args, **kwargs):


@pytest.fixture
async def zigpy_device_tuya(zha_gateway: Gateway, zigpy_device_mock, device_joined):
async def zigpy_device_tuya(zigpy_device_mock, device_joined):
"""Device tracker zigpy tuya device."""

zigpy_dev = zigpy_device_mock(
Expand All @@ -414,7 +419,9 @@ async def zigpy_device_tuya(zha_gateway: Gateway, zigpy_device_mock, device_join


async def test_switch_configurable(
zha_gateway: Gateway, device_joined, zigpy_device_tuya
zha_gateway: Gateway,
device_joined,
zigpy_device_tuya, # pylint: disable=redefined-outer-name
) -> None:
"""Test ZHA configurable switch platform."""

Expand Down Expand Up @@ -470,24 +477,17 @@ async def test_switch_configurable(
await entity.async_update()
await zha_gateway.async_block_till_done()
# the mocking doesn't update the attr cache so this flips back to initial value
assert cluster.read_attributes.call_count == 2
assert cluster.read_attributes.call_count == 1
assert [
call(
[
"window_detection_function",
],
allow_cache=False,
only_cache=False,
manufacturer=None,
),
call(
[
"window_detection_function_inverter",
],
allow_cache=False,
only_cache=False,
manufacturer=None,
),
)
] == cluster.read_attributes.call_args_list

cluster.write_attributes.reset_mock()
Expand Down
8 changes: 6 additions & 2 deletions zha/application/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ def destination_address(self) -> zdo_types.MultiAddress:


async def safe_read(
cluster, attributes, allow_cache=True, only_cache=False, manufacturer=None
cluster: zigpy.zcl.Cluster,
attributes: list[int | str],
allow_cache: bool = True,
only_cache: bool = False,
manufacturer=None,
):
"""Swallow all exceptions from network read.
Expand Down Expand Up @@ -157,7 +161,7 @@ def convert_to_zcl_values(
return converted_fields


def async_is_bindable_target(source_zha_device, target_zha_device):
def async_is_bindable_target(source_zha_device: Device, target_zha_device: Device):
"""Determine if target is bindable to source."""
if target_zha_device.nwk == 0x0000:
return True
Expand Down
5 changes: 5 additions & 0 deletions zha/application/platforms/binary_sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ async def async_update(self) -> None:
"""Attempt to retrieve on off state from the binary sensor."""
await super().async_update()
attribute = getattr(self._cluster_handler, "value_attribute", "on_off")
# this is a cached read to get the value for state mgt so there is no double read
attr_value = await self._cluster_handler.get_attribute_value(attribute)
if attr_value is not None:
self._state = attr_value
Expand Down Expand Up @@ -240,6 +241,10 @@ def parse(value: bool | int) -> bool:
"""Parse the raw attribute into a bool state."""
return BinarySensor.parse(value & 3) # use only bit 0 and 1 for alarm state

async def async_update(self) -> None:
"""Attempt to retrieve on off state from the IAS Zone sensor."""
await PlatformEntity.async_update(self)


@STRICT_MATCH(cluster_handler_names=CLUSTER_HANDLER_ZONE, models={"WL4200", "WL4200S"})
class SinopeLeakStatus(BinarySensor):
Expand Down
10 changes: 0 additions & 10 deletions zha/application/platforms/number/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,16 +129,6 @@ async def async_set_native_value(self, value: float) -> None:
await self._analog_output_cluster_handler.async_set_present_value(float(value))
self.maybe_emit_state_changed_event()

async def async_update(self) -> None:
"""Attempt to retrieve the state of the entity."""
await super().async_update()
_LOGGER.debug("polling current state")
if self._analog_output_cluster_handler:
value = await self._analog_output_cluster_handler.get_attribute_value(
"present_value", from_cache=False
)
_LOGGER.debug("read value=%s", value)

def handle_cluster_handler_attribute_updated(
self,
event: ClusterAttributeUpdatedEvent, # pylint: disable=unused-argument
Expand Down
1 change: 0 additions & 1 deletion zha/application/platforms/sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,6 @@ async def _refresh(self):
if self._device.available and self._device.gateway.config.allow_polling:
self.debug("polling for updated state")
await self.async_update()
self.maybe_emit_state_changed_event()
else:
self.debug(
"skipping polling for updated state, available: %s, allow polled requests: %s",
Expand Down
15 changes: 9 additions & 6 deletions zha/application/platforms/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,13 +286,16 @@ async def async_turn_off(self, **kwargs: Any) -> None: # pylint: disable=unused
async def async_update(self) -> None:
"""Attempt to retrieve the state of the entity."""
self.debug("Polling current state")
value = await self._cluster_handler.get_attribute_value(
self._attribute_name, from_cache=False
)
await self._cluster_handler.get_attribute_value(
self._inverter_attribute_name, from_cache=False
results = await self._cluster_handler.get_attributes(
[
self._attribute_name,
self._inverter_attribute_name,
],
from_cache=False,
only_cache=False,
)
self.debug("read value=%s, inverted=%s", value, self.inverted)

self.debug("read values=%s", results)
self.maybe_emit_state_changed_event()

def get_state(self) -> dict:
Expand Down
2 changes: 1 addition & 1 deletion zha/zigbee/cluster_handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def __init__(self, cluster: zigpy.zcl.Cluster, endpoint: Endpoint) -> None:
attr_def: ZCLAttributeDef = self.cluster.attributes_by_name[
self.REPORT_CONFIG[0]["attr"]
]
self.value_attribute = attr_def.id
self.value_attribute = attr_def.name
self._status: ClusterHandlerStatus = ClusterHandlerStatus.CREATED
self._cluster.add_listener(self)
self.data_cache: dict[str, Any] = {}
Expand Down
4 changes: 2 additions & 2 deletions zha/zigbee/cluster_handlers/closures.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
class DoorLockClusterHandler(ClusterHandler):
"""Door lock cluster handler."""

_value_attribute = 0
_value_attribute: str = DoorLock.AttributeDefs.lock_state.name
REPORT_CONFIG = (
AttrReportConfig(
attr=DoorLock.AttributeDefs.lock_state.name,
Expand Down Expand Up @@ -75,7 +75,7 @@ def attribute_updated(self, attrid: int, value: Any, _: Any) -> None:
self.debug(
"Attribute report '%s'[%s] = %s", self.cluster.name, attr_name, value
)
if attrid == self._value_attribute:
if attr_name == self._value_attribute:
self.emit(
CLUSTER_HANDLER_EVENT,
ClusterAttributeUpdatedEvent(
Expand Down
9 changes: 7 additions & 2 deletions zha/zigbee/cluster_handlers/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ async def async_set_present_value(self, value: float) -> None:
{AnalogOutput.AttributeDefs.present_value.name: value}
)

async def async_update(self):
"""Update cluster value attribute."""
await self.get_attribute_value(
AnalogOutput.AttributeDefs.present_value.name, from_cache=False
)


@registries.CLUSTER_HANDLER_REGISTRY.register(AnalogValue.cluster_id)
class AnalogValueClusterHandler(ClusterHandler):
Expand Down Expand Up @@ -528,9 +534,8 @@ async def async_update(self):
from_cache = not self._endpoint.device.is_mains_powered
self.debug("attempting to update onoff state - from cache: %s", from_cache)
await self.get_attribute_value(
OnOff.AttributeDefs.on_off.id, from_cache=from_cache
OnOff.AttributeDefs.on_off.name, from_cache=from_cache
)
await super().async_update()


@registries.CLUSTER_HANDLER_REGISTRY.register(OnOffConfiguration.cluster_id)
Expand Down
Loading

0 comments on commit b984e08

Please sign in to comment.