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 9b057a3cbc3b..5ec829fcb057 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 045e6a8f5939..14401b260b2b 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 9375ecf60b15..608a256606f4 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 58f2a608e47a..3736858d599e 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 d3c8fc0b29db..f4689460f936 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 0c9ff765710b..3db54712deef 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 fe2a43f7334d..afc73baca70b 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"