From c173a3be44f3486a8b1e1329325facf346282b25 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sun, 16 Jun 2019 13:17:53 -0400 Subject: [PATCH] Misc. ZHA enhancements (#24559) * add nwk to device info * input bind only cluster support * cleanup channel only clusters * dirty hack to correct xiaomi vibration sensor * exclude remaining remote binary sensors * review comments * fix comment --- homeassistant/components/zha/core/device.py | 5 +- .../components/zha/core/discovery.py | 50 ++++++++++++++----- homeassistant/components/zha/core/gateway.py | 6 ++- .../components/zha/core/registries.py | 37 ++++++++++++-- 4 files changed, 78 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 85373517aa2..dcb4fe7ca0e 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -18,7 +18,7 @@ from .const import ( ATTR_COMMAND_TYPE, ATTR_ARGS, CLIENT_COMMANDS, SERVER_COMMANDS, ATTR_ENDPOINT_ID, IEEE, MODEL, NAME, UNKNOWN, QUIRK_APPLIED, QUIRK_CLASS, ZDO_CHANNEL, MANUFACTURER_CODE, POWER_SOURCE, MAINS_POWERED, - BATTERY_OR_UNKNOWN + BATTERY_OR_UNKNOWN, NWK ) from .channels import EventRelayChannel @@ -189,6 +189,7 @@ class ZHADevice: ieee = str(self.ieee) return { IEEE: ieee, + NWK: self.nwk, ATTR_MANUFACTURER: self.manufacturer, MODEL: self.model, NAME: self.name or ieee, @@ -390,7 +391,7 @@ class ZHADevice: manufacturer=manufacturer ) _LOGGER.debug( - 'set: %s for attr: %s to cluster: %s for entity: %s - res: %s', + 'set: %s for attr: %s to cluster: %s for ept: %s - res: %s', value, attribute, cluster_id, diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index e81fa53020d..8901726ff88 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -21,9 +21,10 @@ from .const import ( SENSOR_TYPE, UNKNOWN, GENERIC, POWER_CONFIGURATION_CHANNEL ) from .registries import ( - BINARY_SENSOR_TYPES, NO_SENSOR_CLUSTERS, EVENT_RELAY_CLUSTERS, + BINARY_SENSOR_TYPES, CHANNEL_ONLY_CLUSTERS, EVENT_RELAY_CLUSTERS, SENSOR_TYPES, DEVICE_CLASS, COMPONENT_CLUSTERS, - SINGLE_INPUT_CLUSTER_DEVICE_CLASS, SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS + SINGLE_INPUT_CLUSTER_DEVICE_CLASS, SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS, + OUTPUT_CHANNEL_ONLY_CLUSTERS, REMOTE_DEVICE_TYPES ) from ..device_entity import ZhaDeviceEntity @@ -87,6 +88,12 @@ def async_process_endpoint( 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.""" + # really ugly hack to deal with xiaomi using the door lock cluster + # incorrectly. + if hasattr(cluster, 'ep_attribute') and \ + cluster.ep_attribute == 'multistate_input': + channel_class = AttributeListeningChannel + # end of ugly hack if channel_class is None: channel_class = ZIGBEE_CHANNEL_REGISTRY.get(cluster.cluster_id, AttributeListeningChannel) @@ -161,17 +168,18 @@ def _async_handle_single_cluster_matches(hass, endpoint, zha_device, profile_clusters, device_key, is_new_join): """Dispatch single cluster matches to HA components.""" + from zigpy.zcl.clusters.general import OnOff cluster_matches = [] 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: + if cluster.cluster_id in CHANNEL_ONLY_CLUSTERS: cluster_match_results.append( _async_handle_channel_only_cluster_match( zha_device, cluster, is_new_join, )) + continue if cluster.cluster_id not in profile_clusters: cluster_match_results.append(_async_handle_single_cluster_match( @@ -184,15 +192,33 @@ def _async_handle_single_cluster_matches(hass, endpoint, zha_device, )) for cluster in endpoint.out_clusters.values(): + if cluster.cluster_id in OUTPUT_CHANNEL_ONLY_CLUSTERS: + cluster_match_results.append( + _async_handle_channel_only_cluster_match( + zha_device, + cluster, + is_new_join, + )) + continue + + device_type = cluster.endpoint.device_type + profile_id = cluster.endpoint.profile_id + if cluster.cluster_id not in profile_clusters: - cluster_match_results.append(_async_handle_single_cluster_match( - hass, - zha_device, - cluster, - device_key, - SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS, - is_new_join, - )) + # prevent remotes and controllers from getting entities + if not (cluster.cluster_id == OnOff.cluster_id and profile_id in + REMOTE_DEVICE_TYPES and device_type in + REMOTE_DEVICE_TYPES[profile_id]): + cluster_match_results.append( + _async_handle_single_cluster_match( + hass, + zha_device, + cluster, + device_key, + SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS, + is_new_join, + ) + ) if cluster.cluster_id in EVENT_RELAY_CLUSTERS: _async_create_cluster_channel( diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index f8458848fc2..d1ccaf8265c 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -32,7 +32,7 @@ from .discovery import ( async_create_device_entity, async_dispatch_discovery_info, async_process_endpoint) from .patches import apply_application_controller_patch -from .registries import RADIO_TYPES +from .registries import RADIO_TYPES, INPUT_BIND_ONLY_CLUSTERS from .store import async_get_registry _LOGGER = logging.getLogger(__name__) @@ -274,8 +274,10 @@ class ZHAGateway: ) if endpoint_id != 0: for cluster in endpoint.in_clusters.values(): - cluster.bind_only = False + cluster.bind_only = \ + cluster.cluster_id in INPUT_BIND_ONLY_CLUSTERS for cluster in endpoint.out_clusters.values(): + # output clusters are always bind only cluster.bind_only = True else: is_rejoin = is_new_join is True diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index 8db60727578..a7b89362de9 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -30,11 +30,14 @@ SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS = {} SENSOR_TYPES = {} RADIO_TYPES = {} BINARY_SENSOR_TYPES = {} +REMOTE_DEVICE_TYPES = {} CLUSTER_REPORT_CONFIGS = {} CUSTOM_CLUSTER_MAPPINGS = {} EVENT_RELAY_CLUSTERS = [] -NO_SENSOR_CLUSTERS = [] +CHANNEL_ONLY_CLUSTERS = [] +OUTPUT_CHANNEL_ONLY_CLUSTERS = [] BINDABLE_CLUSTERS = [] +INPUT_BIND_ONLY_CLUSTERS = [] BINARY_SENSOR_CLUSTERS = set() LIGHT_CLUSTERS = set() SWITCH_CLUSTERS = set() @@ -59,6 +62,11 @@ def establish_device_mappings(): if zll.PROFILE_ID not in DEVICE_CLASS: DEVICE_CLASS[zll.PROFILE_ID] = {} + if zha.PROFILE_ID not in REMOTE_DEVICE_TYPES: + REMOTE_DEVICE_TYPES[zha.PROFILE_ID] = [] + if zll.PROFILE_ID not in REMOTE_DEVICE_TYPES: + REMOTE_DEVICE_TYPES[zll.PROFILE_ID] = [] + def get_ezsp_radio(): import bellows.ezsp from bellows.zigbee.application import ControllerApplication @@ -101,15 +109,21 @@ def establish_device_mappings(): EVENT_RELAY_CLUSTERS.append(zcl.clusters.general.LevelControl.cluster_id) EVENT_RELAY_CLUSTERS.append(zcl.clusters.general.OnOff.cluster_id) - NO_SENSOR_CLUSTERS.append(zcl.clusters.general.Basic.cluster_id) - NO_SENSOR_CLUSTERS.append( + CHANNEL_ONLY_CLUSTERS.append(zcl.clusters.general.Basic.cluster_id) + CHANNEL_ONLY_CLUSTERS.append( zcl.clusters.general.PowerConfiguration.cluster_id) - NO_SENSOR_CLUSTERS.append(zcl.clusters.lightlink.LightLink.cluster_id) + CHANNEL_ONLY_CLUSTERS.append(zcl.clusters.lightlink.LightLink.cluster_id) + + OUTPUT_CHANNEL_ONLY_CLUSTERS.append(zcl.clusters.general.Scenes.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) + INPUT_BIND_ONLY_CLUSTERS.append( + zcl.clusters.lightlink.LightLink.cluster_id + ) + DEVICE_CLASS[zha.PROFILE_ID].update({ zha.DeviceType.SMART_PLUG: SWITCH, zha.DeviceType.LEVEL_CONTROLLABLE_OUTPUT: LIGHT, @@ -181,6 +195,21 @@ def establish_device_mappings(): SMARTTHINGS_ACCELERATION_CLUSTER: ACCELERATION, }) + zhap = zha.PROFILE_ID + REMOTE_DEVICE_TYPES[zhap].append(zha.DeviceType.NON_COLOR_SCENE_CONTROLLER) + REMOTE_DEVICE_TYPES[zhap].append(zha.DeviceType.NON_COLOR_CONTROLLER) + REMOTE_DEVICE_TYPES[zhap].append(zha.DeviceType.COLOR_SCENE_CONTROLLER) + REMOTE_DEVICE_TYPES[zhap].append(zha.DeviceType.COLOR_CONTROLLER) + REMOTE_DEVICE_TYPES[zhap].append(zha.DeviceType.REMOTE_CONTROL) + REMOTE_DEVICE_TYPES[zhap].append(zha.DeviceType.SCENE_SELECTOR) + + zllp = zll.PROFILE_ID + REMOTE_DEVICE_TYPES[zllp].append(zll.DeviceType.COLOR_CONTROLLER) + REMOTE_DEVICE_TYPES[zllp].append(zll.DeviceType.COLOR_SCENE_CONTROLLER) + REMOTE_DEVICE_TYPES[zllp].append(zll.DeviceType.CONTROLLER) + REMOTE_DEVICE_TYPES[zllp].append(zll.DeviceType.SCENE_CONTROLLER) + REMOTE_DEVICE_TYPES[zllp].append(zll.DeviceType.CONTROL_BRIDGE) + CLUSTER_REPORT_CONFIGS.update({ zcl.clusters.general.Alarms.cluster_id: [], zcl.clusters.general.Basic.cluster_id: [],