From 32b99cc31608e7b70aadfca3268faf822f67a13f Mon Sep 17 00:00:00 2001 From: Aaron Gorka Date: Wed, 30 Oct 2024 22:08:04 +1100 Subject: [PATCH] Add quirk for the TS0207 rain solar sensor Adds a quirk that fixes this device by changing the "moving" and "closed" sensors to the rain and illuminance sensors that the device actually has. Most of the credit for this code goes to @hadeshimself from [this](https://github.com/zigpy/zha-device-handlers/issues/3249#issuecomment-2282969644) comment. --- zhaquirks/tuya/ts0207.py | 197 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100755 zhaquirks/tuya/ts0207.py diff --git a/zhaquirks/tuya/ts0207.py b/zhaquirks/tuya/ts0207.py new file mode 100755 index 0000000000..6636747eda --- /dev/null +++ b/zhaquirks/tuya/ts0207.py @@ -0,0 +1,197 @@ +"""Quirk for TS0207 rain sensors.""" + +import zigpy.types as t +from zigpy.profiles import zha +from zigpy.quirks import CustomCluster +from zigpy.zcl.clusters.general import ( + Basic, + Groups, + Identify, + OnOff, + Ota, + PowerConfiguration, + Scenes, + Time, +) +from zigpy.zcl.clusters.lightlink import LightLink +from zigpy.zcl.clusters.measurement import IlluminanceMeasurement +from zigpy.zcl.clusters.security import IasZone +from zhaquirks.const import ( + DEVICE_TYPE, + ENDPOINTS, + INPUT_CLUSTERS, + MODELS_INFO, + OUTPUT_CLUSTERS, + PROFILE_ID, +) +from zhaquirks.tuya.mcu import TuyaMCUCluster +from zhaquirks.tuya import ( + DPToAttributeMapping, + EnchantedDevice, + TuyaNoBindPowerConfigurationCluster, +) +from zhaquirks import Bus +import logging + +ZONE_TYPE = 0x0001 + +LOGGER = logging.getLogger(__name__) + + +class TuyaSolarRainSensorCluster(TuyaMCUCluster, CustomCluster): + """Tuya manufacturer cluster.""" + + attributes = TuyaMCUCluster.attributes.copy() + attributes.update( + { + 0xEF65: ("light_intensity", t.uint32_t, True), + 0xEF66: ("average_light_intensity_20mins", t.uint32_t, True), + 0xEF67: ("todays_max_light_intensity", t.uint32_t, True), + 0xEF68: ("cleaning_reminder", t.Bool, True), + 0xEF69: ("rain_sensor_voltage", t.uint32_t, True), + } + ) + + dp_to_attribute: dict[int, DPToAttributeMapping] = { + 101: DPToAttributeMapping( + TuyaMCUCluster.ep_attribute, + "light_intensity", + ), + 102: DPToAttributeMapping( + TuyaMCUCluster.ep_attribute, + "average_light_intensity_20mins", + ), + 103: DPToAttributeMapping( + TuyaMCUCluster.ep_attribute, + "todays_max_light_intensity", + ), + 104: DPToAttributeMapping( + TuyaMCUCluster.ep_attribute, + "cleaning_reminder", + ), + 105: DPToAttributeMapping( + TuyaMCUCluster.ep_attribute, + "rain_sensor_voltage", + ), + } + + data_point_handlers = { + 101: "_dp_2_attr_update", + 102: "_dp_2_attr_update", + 103: "_dp_2_attr_update", + 104: "_dp_2_attr_update", + 105: "_dp_2_attr_update", + } + + def _update_attribute(self, attrid, value): + super()._update_attribute(attrid, value) + LOGGER.debug(f"Updating attr {hex(attrid)}...") + if attrid == 0xEF65 and value is not None and value >= 0: + LOGGER.debug( + f"Creating illuminance_reported event on illuminance_bus with attrid {hex(attrid)}..." + ) + try: + self.endpoint.device.illuminance_bus.listener_event( + "illuminance_reported", value + ) + except: + LOGGER.exception("Failed to create illuminance_reported event...") + pass + + +class TuyaIlluminanceCluster(CustomCluster, IlluminanceMeasurement): + """Tuya Illuminance cluster.""" + + cluster_id = IlluminanceMeasurement.cluster_id + MEASURED_VALUE_ATTR_ID = 0x0000 + MIN_MEASURED_VALUE_ATTR_ID = 0x0001 + MAX_MEASURED_VALUE_ATTR_ID = 0x0001 + + # The value gets passed through `round(pow(10, ((value - 1) / 10000)))` by ZHA + # https://github.com/zigpy/zha/blob/927e249134c87bd7805139c8fb61e048593ec155/zha/application/platforms/sensor/__init__.py#L782C9-L782C53 + CALIBRATION_FACTOR = 7 # very approximate adjustment, do not expect this to be accurate + + def __init__(self, *args, **kwargs): + """Init.""" + super().__init__(*args, **kwargs) + LOGGER.debug("Attaching TuyaIlluminanceCluster to listener...") + try: + self.endpoint.device.illuminance_bus.add_listener(self) + LOGGER.debug("TuyaIlluminanceCluster attached to listener.") + except: + LOGGER.exception("Failed to add listener.") + pass + + def illuminance_reported(self, value): + """Illuminance reported.""" + + calibrated_value = value * self.CALIBRATION_FACTOR + LOGGER.debug( + f"Received illuminance_reported event with value {value}, updating attribute with calibrated value {calibrated_value}..." + ) + self._update_attribute(self.MEASURED_VALUE_ATTR_ID, calibrated_value) + LOGGER.debug(f"measured_value attribute updated.") + + +class TuyaIasZone(CustomCluster, IasZone): + """IAS Zone for rain sensors.""" + + _CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Water_Sensor} + + +class TuyaSolarRainSensor(EnchantedDevice): + """TS0207 Rain sensor quirk.""" + + def __init__(self, *args, **kwargs) -> None: + """Init.""" + self.illuminance_bus = Bus() + LOGGER.debug(f"Bus created.") + super().__init__(*args, **kwargs) + + signature = { + MODELS_INFO: [("_TZ3210_tgvtvdoc", "TS0207")], + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.IAS_ZONE, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration.cluster_id, + Groups.cluster_id, + Scenes.cluster_id, + IasZone.cluster_id, + TuyaMCUCluster.cluster_id, + ], + OUTPUT_CLUSTERS: [ + Identify.cluster_id, + Groups.cluster_id, + OnOff.cluster_id, + Time.cluster_id, + Ota.cluster_id, + LightLink.cluster_id, + ], + }, + }, + } + + replacement = { + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.IAS_ZONE, + INPUT_CLUSTERS: [ + Basic.cluster_id, + Groups.cluster_id, + Scenes.cluster_id, + TuyaNoBindPowerConfigurationCluster, + TuyaIasZone, + TuyaSolarRainSensorCluster, + TuyaIlluminanceCluster, + ], + OUTPUT_CLUSTERS: [ + Time.cluster_id, + Ota.cluster_id, + ], + }, + }, + }