From ac8f555a70d90e49af3aa0b72d6f256f28bcfdf2 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Tue, 30 Jan 2024 22:40:33 -0500 Subject: [PATCH] Add additional entities for the Aqara E1 curtain motor to ZHA (#108243) * aqara curtain motor opened by hand binary sensor add icon and translation key for identify button remove previous inversion entity add window covering type sensor and aqara curtain motor sensors add aqara curtain motor hook lock switch add aqara curtain motor attributes zcl_init_attrs add aqara curtain motor zcl_init_attrs translations * update translation string * review comments * use enum sensor after rebase * remove button change --- homeassistant/components/zha/binary_sensor.py | 15 ++++- .../zha/core/cluster_handlers/general.py | 3 + .../cluster_handlers/manufacturerspecific.py | 8 +++ homeassistant/components/zha/select.py | 19 ------- homeassistant/components/zha/sensor.py | 55 +++++++++++++++++++ homeassistant/components/zha/strings.json | 15 +++++ homeassistant/components/zha/switch.py | 12 ++++ 7 files changed, 107 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 9b057a3cbc3..5ec829fcb05 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -74,7 +74,7 @@ class BinarySensor(ZhaEntity, BinarySensorEntity): _attribute_name: str - def __init__(self, unique_id, zha_device, cluster_handlers, **kwargs): + def __init__(self, unique_id, zha_device, cluster_handlers, **kwargs) -> None: """Initialize the ZHA binary sensor.""" super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) self._cluster_handler = cluster_handlers[0] @@ -336,3 +336,16 @@ class AqaraLinkageAlarmState(BinarySensor): _unique_id_suffix = "linkage_alarm_state" _attr_device_class: BinarySensorDeviceClass = BinarySensorDeviceClass.SMOKE _attr_translation_key: str = "linkage_alarm_state" + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names="opple_cluster", models={"lumi.curtain.agl001"} +) +class AqaraE1CurtainMotorOpenedByHandBinarySensor(BinarySensor): + """Opened by hand binary sensor.""" + + _unique_id_suffix = "hand_open" + _attribute_name = "hand_open" + _attr_translation_key = "hand_open" + _attr_icon = "mdi:hand-wave" + _attr_entity_category = EntityCategory.DIAGNOSTIC diff --git a/homeassistant/components/zha/core/cluster_handlers/general.py b/homeassistant/components/zha/core/cluster_handlers/general.py index 045e6a8f593..14401b260b2 100644 --- a/homeassistant/components/zha/core/cluster_handlers/general.py +++ b/homeassistant/components/zha/core/cluster_handlers/general.py @@ -203,6 +203,9 @@ class BasicClusterHandler(ClusterHandler): ): self.ZCL_INIT_ATTRS = self.ZCL_INIT_ATTRS.copy() self.ZCL_INIT_ATTRS["transmit_power"] = True + elif self.cluster.endpoint.model == "lumi.curtain.agl001": + self.ZCL_INIT_ATTRS = self.ZCL_INIT_ATTRS.copy() + self.ZCL_INIT_ATTRS["power_source"] = True @registries.ZIGBEE_CLUSTER_HANDLER_REGISTRY.register(BinaryInput.cluster_id) diff --git a/homeassistant/components/zha/core/cluster_handlers/manufacturerspecific.py b/homeassistant/components/zha/core/cluster_handlers/manufacturerspecific.py index 9375ecf60b1..608a256606f 100644 --- a/homeassistant/components/zha/core/cluster_handlers/manufacturerspecific.py +++ b/homeassistant/components/zha/core/cluster_handlers/manufacturerspecific.py @@ -160,6 +160,14 @@ class OppleRemoteClusterHandler(ClusterHandler): "startup_on_off": True, "decoupled_mode": True, } + elif self.cluster.endpoint.model == "lumi.curtain.agl001": + self.ZCL_INIT_ATTRS = { + "hooks_state": True, + "hooks_lock": True, + "positions_stored": True, + "light_level": True, + "hand_open": True, + } async def async_initialize_cluster_handler_specific(self, from_cache: bool) -> None: """Initialize cluster handler specific.""" diff --git a/homeassistant/components/zha/select.py b/homeassistant/components/zha/select.py index 58f2a608e47..3736858d599 100644 --- a/homeassistant/components/zha/select.py +++ b/homeassistant/components/zha/select.py @@ -471,25 +471,6 @@ class AqaraT2RelayDecoupledMode(ZCLEnumSelectEntity): _attr_translation_key: str = "decoupled_mode" -class AqaraE1ReverseDirection(types.enum8): - """Aqara curtain reversal.""" - - Normal = 0x00 - Inverted = 0x01 - - -@CONFIG_DIAGNOSTIC_MATCH( - cluster_handler_names="window_covering", models={"lumi.curtain.agl001"} -) -class AqaraCurtainMode(ZCLEnumSelectEntity): - """Representation of a ZHA curtain mode configuration entity.""" - - _unique_id_suffix = "window_covering_mode" - _attribute_name = "window_covering_mode" - _enum = AqaraE1ReverseDirection - _attr_translation_key: str = "window_covering_mode" - - class InovelliOutputMode(types.enum1): """Inovelli output mode.""" diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index d3c8fc0b29d..f4689460f93 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -10,6 +10,8 @@ import random from typing import TYPE_CHECKING, Any, Self from zigpy import types +from zigpy.zcl.clusters.closures import WindowCovering +from zigpy.zcl.clusters.general import Basic from homeassistant.components.climate import HVACAction from homeassistant.components.sensor import ( @@ -51,6 +53,7 @@ from .core import discovery from .core.const import ( CLUSTER_HANDLER_ANALOG_INPUT, CLUSTER_HANDLER_BASIC, + CLUSTER_HANDLER_COVER, CLUSTER_HANDLER_DEVICE_TEMPERATURE, CLUSTER_HANDLER_ELECTRICAL_MEASUREMENT, CLUSTER_HANDLER_HUMIDITY, @@ -1312,3 +1315,55 @@ class SetpointChangeSource(EnumSensor): _attr_icon: str = "mdi:thermostat" _attr_entity_category = EntityCategory.DIAGNOSTIC _enum = SetpointChangeSourceEnum + + +@CONFIG_DIAGNOSTIC_MATCH(cluster_handler_names=CLUSTER_HANDLER_COVER) +# pylint: disable-next=hass-invalid-inheritance # needs fixing +class WindowCoveringTypeSensor(EnumSensor): + """Sensor that displays the type of a cover device.""" + + _attribute_name: str = WindowCovering.AttributeDefs.window_covering_type.name + _enum = WindowCovering.WindowCoveringType + _unique_id_suffix: str = WindowCovering.AttributeDefs.window_covering_type.name + _attr_translation_key: str = WindowCovering.AttributeDefs.window_covering_type.name + _attr_entity_category = EntityCategory.DIAGNOSTIC + _attr_icon = "mdi:curtains" + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names=CLUSTER_HANDLER_BASIC, models={"lumi.curtain.agl001"} +) +# pylint: disable-next=hass-invalid-inheritance # needs fixing +class AqaraCurtainMotorPowerSourceSensor(EnumSensor): + """Sensor that displays the power source of the Aqara E1 curtain motor device.""" + + _attribute_name: str = Basic.AttributeDefs.power_source.name + _enum = Basic.PowerSource + _unique_id_suffix: str = Basic.AttributeDefs.power_source.name + _attr_translation_key: str = Basic.AttributeDefs.power_source.name + _attr_entity_category = EntityCategory.DIAGNOSTIC + _attr_icon = "mdi:battery-positive" + + +class AqaraE1HookState(types.enum8): + """Aqara hook state.""" + + Unlocked = 0x00 + Locked = 0x01 + Locking = 0x02 + Unlocking = 0x03 + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names="opple_cluster", models={"lumi.curtain.agl001"} +) +# pylint: disable-next=hass-invalid-inheritance # needs fixing +class AqaraCurtainHookStateSensor(EnumSensor): + """Representation of a ZHA curtain mode configuration entity.""" + + _attribute_name = "hooks_state" + _enum = AqaraE1HookState + _unique_id_suffix = "hooks_state" + _attr_translation_key: str = "hooks_state" + _attr_icon: str = "mdi:hook" + _attr_entity_category = EntityCategory.DIAGNOSTIC diff --git a/homeassistant/components/zha/strings.json b/homeassistant/components/zha/strings.json index 0c9ff765710..3db54712dee 100644 --- a/homeassistant/components/zha/strings.json +++ b/homeassistant/components/zha/strings.json @@ -566,6 +566,9 @@ }, "ias_zone": { "name": "IAS zone" + }, + "hand_open": { + "name": "Opened by hand" } }, "button": { @@ -896,6 +899,15 @@ }, "setpoint_change_source": { "name": "Setpoint change source" + }, + "power_source": { + "name": "Power source" + }, + "window_covering_type": { + "name": "Window covering type" + }, + "hooks_state": { + "name": "Hooks state" } }, "switch": { @@ -923,6 +935,9 @@ "inverted": { "name": "Inverted" }, + "hooks_locked": { + "name": "Hooks locked" + }, "smart_bulb_mode": { "name": "Smart bulb mode" }, diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index fe2a43f7334..afc73baca70 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -685,3 +685,15 @@ class WindowCoveringInversionSwitch(ZHASwitchConfigurationEntity): if send_command: await self._cluster_handler.write_attributes_safe({name: current_mode}) await self.async_update() + + +@CONFIG_DIAGNOSTIC_MATCH( + cluster_handler_names="opple_cluster", models={"lumi.curtain.agl001"} +) +class AqaraE1CurtainMotorHooksLockedSwitch(ZHASwitchConfigurationEntity): + """Representation of a switch that controls whether the curtain motor hooks are locked.""" + + _unique_id_suffix = "hooks_lock" + _attribute_name = "hooks_lock" + _attr_translation_key = "hooks_locked" + _attr_icon: str = "mdi:lock"