From 45316f6ed6821729b8f6c0be4a7950c9d2b6da24 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sat, 2 Mar 2019 14:09:01 -0500 Subject: [PATCH] ZHA fixes (#21592) * do not report on 0x1000 LightLink cluster * don't flood Zigbee network during configuration or initialization * add lifeline of 60 minutes to lights * use ootb polling --- .../components/zha/core/channels/general.py | 7 +++++++ .../components/zha/core/channels/registry.py | 1 + homeassistant/components/zha/core/device.py | 18 ++++++++++-------- homeassistant/components/zha/core/gateway.py | 4 ++++ homeassistant/components/zha/light.py | 12 ++++++++++++ homeassistant/components/zha/switch.py | 12 ++++++++++-- 6 files changed, 44 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/zha/core/channels/general.py b/homeassistant/components/zha/core/channels/general.py index 621b0ccbee1..cd16fe5d22e 100644 --- a/homeassistant/components/zha/core/channels/general.py +++ b/homeassistant/components/zha/core/channels/general.py @@ -64,6 +64,13 @@ class OnOffChannel(ZigbeeChannel): await self.get_attribute_value(self.ON_OFF, from_cache=from_cache)) await super().async_initialize(from_cache) + async def async_update(self): + """Initialize channel.""" + _LOGGER.debug("Attempting to update onoff state") + self._state = bool( + await self.get_attribute_value(self.ON_OFF, from_cache=False)) + await super().async_update() + class LevelControlChannel(ZigbeeChannel): """Channel for the LevelControl Zigbee cluster.""" diff --git a/homeassistant/components/zha/core/channels/registry.py b/homeassistant/components/zha/core/channels/registry.py index f0363ac8330..8f7335d82a9 100644 --- a/homeassistant/components/zha/core/channels/registry.py +++ b/homeassistant/components/zha/core/channels/registry.py @@ -43,4 +43,5 @@ def populate_channel_registry(): zcl.clusters.general.Basic.cluster_id: BasicChannel, zcl.clusters.security.IasZone.cluster_id: IASZoneChannel, zcl.clusters.hvac.Fan.cluster_id: FanChannel, + zcl.clusters.lightlink.LightLink.cluster_id: ZigbeeChannel, }) diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index b0b41a87809..06b33a418fb 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -205,20 +205,22 @@ class ZHADevice: async def _execute_channel_tasks(self, task_name, *args): """Gather and execute a set of CHANNEL tasks.""" channel_tasks = [] + semaphore = asyncio.Semaphore(3) for channel in self.all_channels: channel_tasks.append( - self._async_create_task(channel, task_name, *args)) + self._async_create_task(semaphore, channel, task_name, *args)) await asyncio.gather(*channel_tasks) - async def _async_create_task(self, channel, func_name, *args): + async def _async_create_task(self, semaphore, channel, func_name, *args): """Configure a single channel on this device.""" try: - await getattr(channel, func_name)(*args) - _LOGGER.debug('%s: channel: %s %s stage succeeded', - self.name, - "{}-{}".format( - channel.name, channel.unique_id), - func_name) + async with semaphore: + await getattr(channel, func_name)(*args) + _LOGGER.debug('%s: channel: %s %s stage succeeded', + self.name, + "{}-{}".format( + channel.name, channel.unique_id), + func_name) except Exception as ex: # pylint: disable=broad-except _LOGGER.warning( '%s channel: %s %s stage failed ex: %s', diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index a498e1e8ee1..595d32b1c2b 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -452,8 +452,11 @@ def establish_device_mappings(): NO_SENSOR_CLUSTERS.append(zcl.clusters.general.Basic.cluster_id) NO_SENSOR_CLUSTERS.append( zcl.clusters.general.PowerConfiguration.cluster_id) + NO_SENSOR_CLUSTERS.append(zcl.clusters.lightlink.LightLink.cluster_id) + BINDABLE_CLUSTERS.append(zcl.clusters.general.LevelControl.cluster_id) BINDABLE_CLUSTERS.append(zcl.clusters.general.OnOff.cluster_id) + BINDABLE_CLUSTERS.append(zcl.clusters.lighting.Color.cluster_id) DEVICE_CLASS[zha.PROFILE_ID].update({ zha.DeviceType.ON_OFF_SWITCH: 'binary_sensor', @@ -537,6 +540,7 @@ def establish_device_mappings(): zcl.clusters.general.PollControl.cluster_id: [], zcl.clusters.general.GreenPowerProxy.cluster_id: [], zcl.clusters.general.OnOffConfiguration.cluster_id: [], + zcl.clusters.lightlink.LightLink.cluster_id: [], zcl.clusters.general.OnOff.cluster_id: [{ 'attr': 'on_off', 'config': REPORT_CONFIG_IMMEDIATE diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index 740d67db1bd..a87912eb213 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -4,6 +4,7 @@ Lights on Zigbee Home Automation networks. For more details on this platform, please refer to the documentation at https://home-assistant.io/components/light.zha/ """ +from datetime import timedelta import logging from homeassistant.components import light @@ -26,6 +27,7 @@ CAPABILITIES_COLOR_XY = 0x08 CAPABILITIES_COLOR_TEMP = 0x10 UNSUPPORTED_ATTRIBUTE = 0x86 +SCAN_INTERVAL = timedelta(minutes=60) async def async_setup_platform(hass, config, async_add_entities, @@ -92,6 +94,11 @@ class Light(ZhaEntity, light.Light): self._supported_features |= light.SUPPORT_COLOR self._hs_color = (0, 0) + @property + def should_poll(self) -> bool: + """Poll state from device.""" + return True + @property def is_on(self) -> bool: """Return true if entity is on.""" @@ -217,3 +224,8 @@ class Light(ZhaEntity, light.Light): return self._state = False self.async_schedule_update_ha_state() + + async def async_update(self): + """Attempt to retrieve on off state from the light.""" + if self._on_off_channel: + await self._on_off_channel.async_update() diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index bdbdd7a6a76..63a0cad93ab 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -71,11 +71,19 @@ class Switch(ZhaEntity, SwitchDevice): async def async_turn_on(self, **kwargs): """Turn the entity on.""" - await self._on_off_channel.on() + success = await self._on_off_channel.on() + if not success: + return + self._state = True + self.async_schedule_update_ha_state() async def async_turn_off(self, **kwargs): """Turn the entity off.""" - await self._on_off_channel.off() + success = await self._on_off_channel.off() + if not success: + return + self._state = False + self.async_schedule_update_ha_state() def async_set_state(self, state): """Handle state update from channel."""