Clean up ZHA post rewrite (#21448)

* update async handling to reduce unnecessary coroutine creation

* lint

* cleanup
This commit is contained in:
David F. Mulcahey 2019-02-26 13:48:10 -05:00 committed by Paulus Schoutsen
parent beb86426e4
commit a34524febe
6 changed files with 110 additions and 91 deletions

View file

@ -154,12 +154,10 @@ async def async_setup_entry(hass, config_entry):
"""Handle message from a device."""
if not sender.initializing and sender.ieee in zha_gateway.devices and \
not zha_gateway.devices[sender.ieee].available:
hass.async_create_task(
zha_gateway.async_device_became_available(
sender, is_reply, profile, cluster, src_ep, dst_ep, tsn,
command_id, args
)
)
return sender.handle_message(
is_reply, profile, cluster, src_ep, dst_ep, tsn, command_id, args)

View file

@ -251,7 +251,7 @@ def async_load_api(hass, application_controller, zha_gateway):
zha_device = zha_gateway.get_device(ieee)
response_clusters = []
if zha_device is not None:
clusters_by_endpoint = await zha_device.get_clusters()
clusters_by_endpoint = zha_device.async_get_clusters()
for ep_id, clusters in clusters_by_endpoint.items():
for c_id, cluster in clusters[IN].items():
response_clusters.append({
@ -289,7 +289,7 @@ def async_load_api(hass, application_controller, zha_gateway):
zha_device = zha_gateway.get_device(ieee)
attributes = None
if zha_device is not None:
attributes = await zha_device.get_cluster_attributes(
attributes = zha_device.async_get_cluster_attributes(
endpoint_id,
cluster_id,
cluster_type)
@ -329,7 +329,7 @@ def async_load_api(hass, application_controller, zha_gateway):
cluster_commands = []
commands = None
if zha_device is not None:
commands = await zha_device.get_cluster_commands(
commands = zha_device.async_get_cluster_commands(
endpoint_id,
cluster_id,
cluster_type)
@ -380,7 +380,7 @@ def async_load_api(hass, application_controller, zha_gateway):
zha_device = zha_gateway.get_device(ieee)
success = failure = None
if zha_device is not None:
cluster = await zha_device.get_cluster(
cluster = zha_device.async_get_cluster(
endpoint_id, cluster_id, cluster_type=cluster_type)
success, failure = await cluster.read_attributes(
[attribute],

View file

@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/zha/
"""
import asyncio
from concurrent.futures import TimeoutError as Timeout
from enum import Enum
from functools import wraps
import logging
@ -55,9 +56,13 @@ def decorate_command(channel, command):
if isinstance(result, bool):
return result
return result[1] is Status.SUCCESS
except DeliveryError:
_LOGGER.debug("%s: command failed: %s", channel.unique_id,
command.__name__)
except (DeliveryError, Timeout) as ex:
_LOGGER.debug(
"%s: command failed: %s exception: %s",
channel.unique_id,
command.__name__,
str(ex)
)
return False
return wrapper

View file

@ -28,8 +28,16 @@ class ColorChannel(ZigbeeChannel):
"""Return the color capabilities."""
return self._color_capabilities
async def async_configure(self):
"""Configure channel."""
await self.fetch_color_capabilities(False)
async def async_initialize(self, from_cache):
"""Initialize channel."""
await self.fetch_color_capabilities(True)
async def fetch_color_capabilities(self, from_cache):
"""Get the color configuration."""
capabilities = await self.get_attribute_value(
'color_capabilities', from_cache=from_cache)

View file

@ -8,6 +8,7 @@ import asyncio
from enum import Enum
import logging
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send
)
@ -188,6 +189,7 @@ class ZHADevice:
"""Initialize channels."""
_LOGGER.debug('%s: started initialization', self.name)
await self._execute_channel_tasks('async_initialize', from_cache)
if BASIC_CHANNEL in self.cluster_channels:
self.power_source = self.cluster_channels.get(
BASIC_CHANNEL).get_power_source()
_LOGGER.debug(
@ -229,7 +231,8 @@ class ZHADevice:
if self._unsub:
self._unsub()
async def get_clusters(self):
@callback
def async_get_clusters(self):
"""Get all clusters for this device."""
return {
ep_id: {
@ -239,25 +242,27 @@ class ZHADevice:
if ep_id != 0
}
async def get_cluster(self, endpoint_id, cluster_id, cluster_type=IN):
@callback
def async_get_cluster(self, endpoint_id, cluster_id, cluster_type=IN):
"""Get zigbee cluster from this entity."""
clusters = await self.get_clusters()
clusters = self.async_get_clusters()
return clusters[endpoint_id][cluster_type][cluster_id]
async def get_cluster_attributes(self, endpoint_id, cluster_id,
@callback
def async_get_cluster_attributes(self, endpoint_id, cluster_id,
cluster_type=IN):
"""Get zigbee attributes for specified cluster."""
cluster = await self.get_cluster(endpoint_id, cluster_id,
cluster = self.async_get_cluster(endpoint_id, cluster_id,
cluster_type)
if cluster is None:
return None
return cluster.attributes
async def get_cluster_commands(self, endpoint_id, cluster_id,
@callback
def async_get_cluster_commands(self, endpoint_id, cluster_id,
cluster_type=IN):
"""Get zigbee commands for specified cluster."""
cluster = await self.get_cluster(endpoint_id, cluster_id,
cluster_type)
cluster = self.async_get_cluster(endpoint_id, cluster_id, cluster_type)
if cluster is None:
return None
return {
@ -269,8 +274,7 @@ class ZHADevice:
attribute, value, cluster_type=IN,
manufacturer=None):
"""Write a value to a zigbee attribute for a cluster in this entity."""
cluster = await self.get_cluster(
endpoint_id, cluster_id, cluster_type)
cluster = self.async_get_cluster(endpoint_id, cluster_id, cluster_type)
if cluster is None:
return None
@ -304,8 +308,7 @@ class ZHADevice:
command_type, args, cluster_type=IN,
manufacturer=None):
"""Issue a command against specified zigbee cluster on this entity."""
cluster = await self.get_cluster(
endpoint_id, cluster_id, cluster_type)
cluster = self.async_get_cluster(endpoint_id, cluster_id, cluster_type)
if cluster is None:
return None
response = None

View file

@ -5,11 +5,11 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/zha/
"""
import asyncio
import collections
import itertools
import logging
from homeassistant import const as ha_const
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity_component import EntityComponent
from . import const as zha_const
@ -122,7 +122,8 @@ class ZHAGateway:
)
)
async def _get_or_create_device(self, zigpy_device):
@callback
def _async_get_or_create_device(self, zigpy_device):
"""Get or create a ZHA device."""
zha_device = self._devices.get(zigpy_device.ieee)
if zha_device is None:
@ -130,12 +131,14 @@ class ZHAGateway:
self._devices[zigpy_device.ieee] = zha_device
return zha_device
async def async_device_became_available(
@callback
def async_device_became_available(
self, sender, is_reply, profile, cluster, src_ep, dst_ep, tsn,
command_id, args):
"""Handle tasks when a device becomes available."""
self.async_update_device(sender)
@callback
def async_update_device(self, sender):
"""Update device that has just become available."""
if sender.ieee in self.devices:
@ -146,34 +149,17 @@ class ZHAGateway:
async def async_device_initialized(self, device, is_new_join):
"""Handle device joined and basic information discovered (async)."""
zha_device = await self._get_or_create_device(device)
zha_device = self._async_get_or_create_device(device)
discovery_infos = []
endpoint_tasks = []
for endpoint_id, endpoint in device.endpoints.items():
endpoint_tasks.append(self._async_process_endpoint(
self._async_process_endpoint(
endpoint_id, endpoint, discovery_infos, device, zha_device,
is_new_join
))
await asyncio.gather(*endpoint_tasks)
await zha_device.async_initialize(from_cache=(not is_new_join))
discovery_tasks = []
for discovery_info in discovery_infos:
discovery_tasks.append(_dispatch_discovery_info(
self._hass,
is_new_join,
discovery_info
))
await asyncio.gather(*discovery_tasks)
device_entity = _create_device_entity(zha_device)
await self._component.async_add_entities([device_entity])
)
if is_new_join:
# because it's a new join we can immediately mark the device as
# available and we already loaded fresh state above
zha_device.update_available(True)
# configure the device
await zha_device.async_configure()
elif not zha_device.available and zha_device.power_source is not None\
and zha_device.power_source != BasicChannel.BATTERY\
and zha_device.power_source != BasicChannel.UNKNOWN:
@ -187,15 +173,33 @@ class ZHAGateway:
)
)
await zha_device.async_initialize(from_cache=False)
else:
await zha_device.async_initialize(from_cache=True)
async def _async_process_endpoint(
for discovery_info in discovery_infos:
_async_dispatch_discovery_info(
self._hass,
is_new_join,
discovery_info
)
device_entity = _async_create_device_entity(zha_device)
await self._component.async_add_entities([device_entity])
if is_new_join:
# because it's a new join we can immediately mark the device as
# available. We do it here because the entities didn't exist above
zha_device.update_available(True)
@callback
def _async_process_endpoint(
self, endpoint_id, endpoint, discovery_infos, device, zha_device,
is_new_join):
"""Process an endpoint on a zigpy device."""
import zigpy.profiles
if endpoint_id == 0: # ZDO
await _create_cluster_channel(
_async_create_cluster_channel(
endpoint,
zha_device,
is_new_join,
@ -226,12 +230,12 @@ class ZHAGateway:
profile_clusters = zha_const.COMPONENT_CLUSTERS[component]
if component and component in COMPONENTS:
profile_match = await _handle_profile_match(
profile_match = _async_handle_profile_match(
self._hass, endpoint, profile_clusters, zha_device,
component, device_key, is_new_join)
discovery_infos.append(profile_match)
discovery_infos.extend(await _handle_single_cluster_matches(
discovery_infos.extend(_async_handle_single_cluster_matches(
self._hass,
endpoint,
zha_device,
@ -241,21 +245,21 @@ class ZHAGateway:
))
async def _create_cluster_channel(cluster, zha_device, is_new_join,
@callback
def _async_create_cluster_channel(cluster, zha_device, is_new_join,
channels=None, channel_class=None):
"""Create a cluster channel and attach it to a device."""
if channel_class is None:
channel_class = ZIGBEE_CHANNEL_REGISTRY.get(cluster.cluster_id,
AttributeListeningChannel)
channel = channel_class(cluster, zha_device)
if is_new_join:
await channel.async_configure()
zha_device.add_cluster_channel(channel)
if channels is not None:
channels.append(channel)
async def _dispatch_discovery_info(hass, is_new_join, discovery_info):
@callback
def _async_dispatch_discovery_info(hass, is_new_join, discovery_info):
"""Dispatch or store discovery information."""
if not discovery_info['channels']:
_LOGGER.warning(
@ -273,7 +277,8 @@ async def _dispatch_discovery_info(hass, is_new_join, discovery_info):
discovery_info
async def _handle_profile_match(hass, endpoint, profile_clusters, zha_device,
@callback
def _async_handle_profile_match(hass, endpoint, profile_clusters, zha_device,
component, device_key, is_new_join):
"""Dispatch a profile match to the appropriate HA component."""
in_clusters = [endpoint.in_clusters[c]
@ -284,17 +289,14 @@ async def _handle_profile_match(hass, endpoint, profile_clusters, zha_device,
if c in endpoint.out_clusters]
channels = []
cluster_tasks = []
for cluster in in_clusters:
cluster_tasks.append(_create_cluster_channel(
cluster, zha_device, is_new_join, channels=channels))
_async_create_cluster_channel(
cluster, zha_device, is_new_join, channels=channels)
for cluster in out_clusters:
cluster_tasks.append(_create_cluster_channel(
cluster, zha_device, is_new_join, channels=channels))
await asyncio.gather(*cluster_tasks)
_async_create_cluster_channel(
cluster, zha_device, is_new_join, channels=channels)
discovery_info = {
'unique_id': device_key,
@ -319,24 +321,25 @@ async def _handle_profile_match(hass, endpoint, profile_clusters, zha_device,
return discovery_info
async def _handle_single_cluster_matches(hass, endpoint, zha_device,
@callback
def _async_handle_single_cluster_matches(hass, endpoint, zha_device,
profile_clusters, device_key,
is_new_join):
"""Dispatch single cluster matches to HA components."""
cluster_matches = []
cluster_match_tasks = []
event_channel_tasks = []
cluster_match_results = []
for cluster in endpoint.in_clusters.values():
# don't let profiles prevent these channels from being created
if cluster.cluster_id in NO_SENSOR_CLUSTERS:
cluster_match_tasks.append(_handle_channel_only_cluster_match(
cluster_match_results.append(
_async_handle_channel_only_cluster_match(
zha_device,
cluster,
is_new_join,
))
if cluster.cluster_id not in profile_clusters[0]:
cluster_match_tasks.append(_handle_single_cluster_match(
cluster_match_results.append(_async_handle_single_cluster_match(
hass,
zha_device,
cluster,
@ -347,7 +350,7 @@ async def _handle_single_cluster_matches(hass, endpoint, zha_device,
for cluster in endpoint.out_clusters.values():
if cluster.cluster_id not in profile_clusters[1]:
cluster_match_tasks.append(_handle_single_cluster_match(
cluster_match_results.append(_async_handle_single_cluster_match(
hass,
zha_device,
cluster,
@ -357,27 +360,28 @@ async def _handle_single_cluster_matches(hass, endpoint, zha_device,
))
if cluster.cluster_id in EVENT_RELAY_CLUSTERS:
event_channel_tasks.append(_create_cluster_channel(
_async_create_cluster_channel(
cluster,
zha_device,
is_new_join,
channel_class=EventRelayChannel
))
await asyncio.gather(*event_channel_tasks)
cluster_match_results = await asyncio.gather(*cluster_match_tasks)
)
for cluster_match in cluster_match_results:
if cluster_match is not None:
cluster_matches.append(cluster_match)
return cluster_matches
async def _handle_channel_only_cluster_match(
@callback
def _async_handle_channel_only_cluster_match(
zha_device, cluster, is_new_join):
"""Handle a channel only cluster match."""
await _create_cluster_channel(cluster, zha_device, is_new_join)
_async_create_cluster_channel(cluster, zha_device, is_new_join)
async def _handle_single_cluster_match(hass, zha_device, cluster, device_key,
@callback
def _async_handle_single_cluster_match(hass, zha_device, cluster, device_key,
device_classes, is_new_join):
"""Dispatch a single cluster match to a HA component."""
component = None # sub_component = None
@ -392,7 +396,7 @@ async def _handle_single_cluster_match(hass, zha_device, cluster, device_key,
if component is None or component not in COMPONENTS:
return
channels = []
await _create_cluster_channel(cluster, zha_device, is_new_join,
_async_create_cluster_channel(cluster, zha_device, is_new_join,
channels=channels)
cluster_key = "{}-{}".format(device_key, cluster.cluster_id)
@ -416,7 +420,8 @@ async def _handle_single_cluster_match(hass, zha_device, cluster, device_key,
return discovery_info
def _create_device_entity(zha_device):
@callback
def _async_create_device_entity(zha_device):
"""Create ZHADeviceEntity."""
device_entity_channels = []
if POWER_CONFIGURATION_CHANNEL in zha_device.cluster_channels: