diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index a40bd62e83c..6c88f3e1013 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -64,6 +64,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class BinarySensor(ZhaEntity, BinarySensorDevice): """ZHA BinarySensor.""" + SENSOR_ATTR = None DEVICE_CLASS = None def __init__(self, unique_id, zha_device, channels, **kwargs): @@ -105,6 +106,8 @@ class BinarySensor(ZhaEntity, BinarySensorDevice): @callback def async_set_state(self, attr_id, attr_name, value): """Set the state.""" + if self.SENSOR_ATTR is None or self.SENSOR_ATTR != attr_name: + return self._state = bool(value) self.async_write_ha_state() @@ -121,6 +124,7 @@ class BinarySensor(ZhaEntity, BinarySensorDevice): class Accelerometer(BinarySensor): """ZHA BinarySensor.""" + SENSOR_ATTR = "acceleration" DEVICE_CLASS = DEVICE_CLASS_MOVING @@ -128,6 +132,7 @@ class Accelerometer(BinarySensor): class Occupancy(BinarySensor): """ZHA BinarySensor.""" + SENSOR_ATTR = "occupancy" DEVICE_CLASS = DEVICE_CLASS_OCCUPANCY @@ -135,6 +140,7 @@ class Occupancy(BinarySensor): class Opening(BinarySensor): """ZHA BinarySensor.""" + SENSOR_ATTR = "on_off" DEVICE_CLASS = DEVICE_CLASS_OPENING @@ -142,6 +148,8 @@ class Opening(BinarySensor): class IASZone(BinarySensor): """ZHA IAS BinarySensor.""" + SENSOR_ATTR = "zone_status" + async def get_device_class(self) -> None: """Get the HA device class from the channel.""" zone_type = await self._channel.get_attribute_value("zone_type") diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 3953db27f20..8182fdcabcf 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -83,6 +83,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class Sensor(ZhaEntity): """Base ZHA sensor.""" + SENSOR_ATTR = None _decimals = 1 _device_class = None _divisor = 1 @@ -126,6 +127,8 @@ class Sensor(ZhaEntity): @callback def async_set_state(self, attr_id, attr_name, value): """Handle state update from channel.""" + if self.SENSOR_ATTR is None or self.SENSOR_ATTR != attr_name: + return if value is not None: value = self.formatter(value) self._state = value @@ -154,6 +157,7 @@ class Sensor(ZhaEntity): class AnalogInput(Sensor): """Sensor that displays analog input values.""" + SENSOR_ATTR = "present_value" pass @@ -161,6 +165,7 @@ class AnalogInput(Sensor): class Battery(Sensor): """Battery sensor of power configuration cluster.""" + SENSOR_ATTR = "battery_percentage_remaining" _device_class = DEVICE_CLASS_BATTERY _unit = UNIT_PERCENTAGE @@ -198,6 +203,7 @@ class Battery(Sensor): class ElectricalMeasurement(Sensor): """Active power measurement.""" + SENSOR_ATTR = "active_power" _device_class = DEVICE_CLASS_POWER _divisor = 10 _unit = POWER_WATT @@ -232,6 +238,7 @@ class Text(Sensor): class Humidity(Sensor): """Humidity sensor.""" + SENSOR_ATTR = "measured_value" _device_class = DEVICE_CLASS_HUMIDITY _divisor = 100 _unit = UNIT_PERCENTAGE @@ -241,6 +248,7 @@ class Humidity(Sensor): class Illuminance(Sensor): """Illuminance Sensor.""" + SENSOR_ATTR = "measured_value" _device_class = DEVICE_CLASS_ILLUMINANCE _unit = "lx" @@ -254,6 +262,7 @@ class Illuminance(Sensor): class SmartEnergyMetering(Sensor): """Metering sensor.""" + SENSOR_ATTR = "instantaneous_demand" _device_class = DEVICE_CLASS_POWER def formatter(self, value): @@ -270,6 +279,7 @@ class SmartEnergyMetering(Sensor): class Pressure(Sensor): """Pressure sensor.""" + SENSOR_ATTR = "measured_value" _device_class = DEVICE_CLASS_PRESSURE _decimals = 0 _unit = "hPa" @@ -279,6 +289,7 @@ class Pressure(Sensor): class Temperature(Sensor): """Temperature Sensor.""" + SENSOR_ATTR = "measured_value" _device_class = DEVICE_CLASS_TEMPERATURE _divisor = 100 _unit = TEMP_CELSIUS diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index 3eb6f407f32..3753136d59d 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -102,6 +102,23 @@ def make_attribute(attrid, value, status=0): return attr +def send_attribute_report(hass, cluster, attrid, value): + """Send a single attribute report.""" + return send_attributes_report(hass, cluster, {attrid: value}) + + +async def send_attributes_report(hass, cluster: int, attributes: dict): + """Cause the sensor to receive an attribute report from the network. + + This is to simulate the normal device communication that happens when a + device is paired to the zigbee network. + """ + attrs = [make_attribute(attrid, value) for attrid, value in attributes.items()] + hdr = make_zcl_header(zcl_f.Command.Report_Attributes) + cluster.handle_message(hdr, [attrs]) + await hass.async_block_till_done() + + async def find_entity_id(domain, zha_device, hass): """Find the entity id under the testing. diff --git a/tests/components/zha/test_binary_sensor.py b/tests/components/zha/test_binary_sensor.py index a22bfa54dae..730c7c844f2 100644 --- a/tests/components/zha/test_binary_sensor.py +++ b/tests/components/zha/test_binary_sensor.py @@ -2,7 +2,6 @@ import pytest import zigpy.zcl.clusters.measurement as measurement import zigpy.zcl.clusters.security as security -import zigpy.zcl.foundation as zcl_f from homeassistant.components.binary_sensor import DOMAIN from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE @@ -11,8 +10,7 @@ from .common import ( async_enable_traffic, async_test_rejoin, find_entity_id, - make_attribute, - make_zcl_header, + send_attributes_report, ) DEVICE_IAS = { @@ -36,17 +34,11 @@ DEVICE_OCCUPANCY = { async def async_test_binary_sensor_on_off(hass, cluster, entity_id): """Test getting on and off messages for binary sensors.""" # binary sensor on - attr = make_attribute(0, 1) - hdr = make_zcl_header(zcl_f.Command.Report_Attributes) - - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() + await send_attributes_report(hass, cluster, {1: 0, 0: 1, 2: 2}) assert hass.states.get(entity_id).state == STATE_ON # binary sensor off - attr.value.value = 0 - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() + await send_attributes_report(hass, cluster, {1: 1, 0: 0, 2: 2}) assert hass.states.get(entity_id).state == STATE_OFF diff --git a/tests/components/zha/test_cover.py b/tests/components/zha/test_cover.py index 3ece16d8116..188ddf69a23 100644 --- a/tests/components/zha/test_cover.py +++ b/tests/components/zha/test_cover.py @@ -14,8 +14,7 @@ from .common import ( async_enable_traffic, async_test_rejoin, find_entity_id, - make_attribute, - make_zcl_header, + send_attributes_report, ) from tests.common import mock_coro @@ -64,19 +63,12 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): await async_enable_traffic(hass, [zha_device]) await hass.async_block_till_done() - attr = make_attribute(8, 100) - hdr = make_zcl_header(zcl_f.Command.Report_Attributes) - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() - # test that the state has changed from unavailable to off + await send_attributes_report(hass, cluster, {0: 0, 8: 100, 1: 1}) assert hass.states.get(entity_id).state == STATE_CLOSED # test to see if it opens - attr = make_attribute(8, 0) - hdr = make_zcl_header(zcl_f.Command.Report_Attributes) - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() + await send_attributes_report(hass, cluster, {0: 1, 8: 0, 1: 100}) assert hass.states.get(entity_id).state == STATE_OPEN # close from UI diff --git a/tests/components/zha/test_device_tracker.py b/tests/components/zha/test_device_tracker.py index 3782cdc09a7..330153e5f8c 100644 --- a/tests/components/zha/test_device_tracker.py +++ b/tests/components/zha/test_device_tracker.py @@ -4,7 +4,6 @@ import time import pytest import zigpy.zcl.clusters.general as general -import zigpy.zcl.foundation as zcl_f from homeassistant.components.device_tracker import DOMAIN, SOURCE_TYPE_ROUTER from homeassistant.components.zha.core.registries import ( @@ -17,8 +16,7 @@ from .common import ( async_enable_traffic, async_test_rejoin, find_entity_id, - make_attribute, - make_zcl_header, + send_attributes_report, ) from tests.common import async_fire_time_changed @@ -66,12 +64,9 @@ async def test_device_tracker(hass, zha_device_joined_restored, zigpy_device_dt) assert hass.states.get(entity_id).state == STATE_NOT_HOME # turn state flip - attr = make_attribute(0x0020, 23) - hdr = make_zcl_header(zcl_f.Command.Report_Attributes) - cluster.handle_message(hdr, [[attr]]) - - attr = make_attribute(0x0021, 200) - cluster.handle_message(hdr, [[attr]]) + await send_attributes_report( + hass, cluster, {0x0000: 0, 0x0020: 23, 0x0021: 200, 0x0001: 2} + ) zigpy_device_dt.last_seen = time.time() + 10 next_update = dt_util.utcnow() + timedelta(seconds=30) diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py index 0cf3e3e954d..5011a847a4e 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -3,7 +3,6 @@ from unittest.mock import call import pytest import zigpy.zcl.clusters.hvac as hvac -import zigpy.zcl.foundation as zcl_f from homeassistant.components import fan from homeassistant.components.fan import ATTR_SPEED, DOMAIN, SERVICE_SET_SPEED @@ -20,8 +19,7 @@ from .common import ( async_enable_traffic, async_test_rejoin, find_entity_id, - make_attribute, - make_zcl_header, + send_attributes_report, ) @@ -52,16 +50,11 @@ async def test_fan(hass, zha_device_joined_restored, zigpy_device): assert hass.states.get(entity_id).state == STATE_OFF # turn on at fan - attr = make_attribute(0, 1) - hdr = make_zcl_header(zcl_f.Command.Report_Attributes) - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() + await send_attributes_report(hass, cluster, {1: 2, 0: 1, 2: 3}) assert hass.states.get(entity_id).state == STATE_ON # turn off at fan - attr.value.value = 0 - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() + await send_attributes_report(hass, cluster, {1: 1, 0: 0, 2: 2}) assert hass.states.get(entity_id).state == STATE_OFF # turn on from HA diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index 6f5bd23e297..f27bd329bdb 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -19,8 +19,7 @@ from .common import ( async_enable_traffic, async_test_rejoin, find_entity_id, - make_attribute, - make_zcl_header, + send_attributes_report, ) from tests.common import async_fire_time_changed @@ -190,26 +189,18 @@ async def test_light( async def async_test_on_off_from_light(hass, cluster, entity_id): """Test on off functionality from the light.""" # turn on at light - attr = make_attribute(0, 1) - hdr = make_zcl_header(zcl_f.Command.Report_Attributes) - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() + await send_attributes_report(hass, cluster, {1: 0, 0: 1, 2: 3}) assert hass.states.get(entity_id).state == STATE_ON # turn off at light - attr.value.value = 0 - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() + await send_attributes_report(hass, cluster, {1: 1, 0: 0, 2: 3}) assert hass.states.get(entity_id).state == STATE_OFF async def async_test_on_from_light(hass, cluster, entity_id): """Test on off functionality from the light.""" # turn on at light - attr = make_attribute(0, 1) - hdr = make_zcl_header(zcl_f.Command.Report_Attributes) - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() + await send_attributes_report(hass, cluster, {1: -1, 0: 1, 2: 2}) assert hass.states.get(entity_id).state == STATE_ON @@ -316,10 +307,10 @@ async def async_test_level_on_off_from_hass( async def async_test_dimmer_from_light(hass, cluster, entity_id, level, expected_state): """Test dimmer functionality from the light.""" - attr = make_attribute(0, level) - hdr = make_zcl_header(zcl_f.Command.Report_Attributes) - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() + + await send_attributes_report( + hass, cluster, {1: level + 10, 0: level, 2: level - 10 or 22} + ) assert hass.states.get(entity_id).state == expected_state # hass uses None for brightness of 0 in state attributes if level == 0: diff --git a/tests/components/zha/test_lock.py b/tests/components/zha/test_lock.py index 0442ea497d7..86ec266ffa2 100644 --- a/tests/components/zha/test_lock.py +++ b/tests/components/zha/test_lock.py @@ -10,12 +10,7 @@ import zigpy.zcl.foundation as zcl_f from homeassistant.components.lock import DOMAIN from homeassistant.const import STATE_LOCKED, STATE_UNAVAILABLE, STATE_UNLOCKED -from .common import ( - async_enable_traffic, - find_entity_id, - make_attribute, - make_zcl_header, -) +from .common import async_enable_traffic, find_entity_id, send_attributes_report from tests.common import mock_coro @@ -58,16 +53,11 @@ async def test_lock(hass, lock): assert hass.states.get(entity_id).state == STATE_UNLOCKED # set state to locked - attr = make_attribute(0, 1) - hdr = make_zcl_header(zcl_f.Command.Report_Attributes) - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() + await send_attributes_report(hass, cluster, {1: 0, 0: 1, 2: 2}) assert hass.states.get(entity_id).state == STATE_LOCKED # set state to unlocked - attr.value.value = 2 - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() + await send_attributes_report(hass, cluster, {1: 0, 0: 2, 2: 3}) assert hass.states.get(entity_id).state == STATE_UNLOCKED # lock from HA diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py index fce882c6949..50b85f5720f 100644 --- a/tests/components/zha/test_sensor.py +++ b/tests/components/zha/test_sensor.py @@ -6,7 +6,6 @@ import zigpy.zcl.clusters.general as general import zigpy.zcl.clusters.homeautomation as homeautomation import zigpy.zcl.clusters.measurement as measurement import zigpy.zcl.clusters.smartenergy as smartenergy -import zigpy.zcl.foundation as zcl_f from homeassistant.components.sensor import DOMAIN import homeassistant.config as config_util @@ -28,38 +27,41 @@ from .common import ( async_enable_traffic, async_test_rejoin, find_entity_id, - make_attribute, - make_zcl_header, + send_attribute_report, + send_attributes_report, ) async def async_test_humidity(hass, cluster, entity_id): """Test humidity sensor.""" - await send_attribute_report(hass, cluster, 0, 1000) + await send_attributes_report(hass, cluster, {1: 1, 0: 1000, 2: 100}) assert_state(hass, entity_id, "10.0", UNIT_PERCENTAGE) async def async_test_temperature(hass, cluster, entity_id): """Test temperature sensor.""" - await send_attribute_report(hass, cluster, 0, 2900) + await send_attributes_report(hass, cluster, {1: 1, 0: 2900, 2: 100}) assert_state(hass, entity_id, "29.0", "°C") async def async_test_pressure(hass, cluster, entity_id): """Test pressure sensor.""" - await send_attribute_report(hass, cluster, 0, 1000) + await send_attributes_report(hass, cluster, {1: 1, 0: 1000, 2: 10000}) + assert_state(hass, entity_id, "1000", "hPa") + + await send_attributes_report(hass, cluster, {0: 1000, 20: -1, 16: 10000}) assert_state(hass, entity_id, "1000", "hPa") async def async_test_illuminance(hass, cluster, entity_id): """Test illuminance sensor.""" - await send_attribute_report(hass, cluster, 0, 10) + await send_attributes_report(hass, cluster, {1: 1, 0: 10, 2: 20}) assert_state(hass, entity_id, "1.0", "lx") async def async_test_metering(hass, cluster, entity_id): """Test metering sensor.""" - await send_attribute_report(hass, cluster, 1024, 12345) + await send_attributes_report(hass, cluster, {1025: 1, 1024: 12345, 1026: 100}) assert_state(hass, entity_id, "12345.0", "unknown") @@ -73,17 +75,17 @@ async def async_test_electrical_measurement(hass, cluster, entity_id): new_callable=mock.PropertyMock, ) as divisor_mock: divisor_mock.return_value = 1 - await send_attribute_report(hass, cluster, 1291, 100) + await send_attributes_report(hass, cluster, {0: 1, 1291: 100, 10: 1000}) assert_state(hass, entity_id, "100", "W") - await send_attribute_report(hass, cluster, 1291, 99) + await send_attributes_report(hass, cluster, {0: 1, 1291: 99, 10: 1000}) assert_state(hass, entity_id, "99", "W") divisor_mock.return_value = 10 - await send_attribute_report(hass, cluster, 1291, 1000) + await send_attributes_report(hass, cluster, {0: 1, 1291: 1000, 10: 5000}) assert_state(hass, entity_id, "100", "W") - await send_attribute_report(hass, cluster, 1291, 99) + await send_attributes_report(hass, cluster, {0: 1, 1291: 99, 10: 5000}) assert_state(hass, entity_id, "9.9", "W") @@ -141,18 +143,6 @@ async def test_sensor( await async_test_rejoin(hass, zigpy_device, [cluster], (report_count,)) -async def send_attribute_report(hass, cluster, attrid, value): - """Cause the sensor to receive an attribute report from the network. - - This is to simulate the normal device communication that happens when a - device is paired to the zigbee network. - """ - attr = make_attribute(attrid, value) - hdr = make_zcl_header(zcl_f.Command.Report_Attributes) - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() - - def assert_state(hass, entity_id, state, unit_of_measurement): """Check that the state is what is expected. diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index 22ceb629009..98f661cc1ab 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -12,8 +12,7 @@ from .common import ( async_enable_traffic, async_test_rejoin, find_entity_id, - make_attribute, - make_zcl_header, + send_attributes_report, ) from tests.common import mock_coro @@ -53,16 +52,11 @@ async def test_switch(hass, zha_device_joined_restored, zigpy_device): assert hass.states.get(entity_id).state == STATE_OFF # turn on at switch - attr = make_attribute(0, 1) - hdr = make_zcl_header(zcl_f.Command.Report_Attributes) - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() + await send_attributes_report(hass, cluster, {1: 0, 0: 1, 2: 2}) assert hass.states.get(entity_id).state == STATE_ON # turn off at switch - attr.value.value = 0 - cluster.handle_message(hdr, [[attr]]) - await hass.async_block_till_done() + await send_attributes_report(hass, cluster, {1: 1, 0: 0, 2: 2}) assert hass.states.get(entity_id).state == STATE_OFF # turn on from HA