Last active
July 25, 2025 23:35
-
-
Save franortiz/f0253489098c2d5b1ccd89de661202d9 to your computer and use it in GitHub Desktop.
zigpy quirk for TS0601 - Circuit Breaker with Current Leakage Detection and Electrical Measurement
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """Tuya 1 Phase Circuit Breaker with Current Leakage Detection and Electrical Measurement.""" | |
| """WIP: Need multi attribute -> DP code for over/under settings""" | |
| from zigpy.quirks.v2 import SensorDeviceClass, SensorStateClass, EntityPlatform, EntityType | |
| from zigpy.quirks.v2.homeassistant import ( | |
| UnitOfElectricCurrent, | |
| UnitOfEnergy, | |
| UnitOfPower, | |
| UnitOfTime | |
| ) | |
| from typing import Any | |
| import zigpy.types as t | |
| from zigpy.zcl import foundation as f | |
| from zigpy.zcl.clusters.general import LevelControl, OnOff, DeviceTemperature | |
| from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement | |
| from zhaquirks.tuya import DPToAttributeMapping, TuyaLocalCluster, PowerOnState | |
| from zhaquirks.tuya.builder import TuyaQuirkBuilder | |
| from zhaquirks.tuya.ts0601_rcbo import ( | |
| TuyaRCBOOnOff, | |
| ) | |
| class FaultCode(t.enum8): | |
| """Fault Code enum.""" | |
| CLEAR = 0x00 | |
| OVERCURRENT = 0x01 | |
| UNDERVOLTAGE = 0x02 | |
| OVERVOLTAGE = 0x04 | |
| OVERLEAKAGECURRENT = 0x08 | |
| OVERTEMPERATURE = 0x10 | |
| class DeviceStatus(t.enum8): | |
| """Tuya Device Status""" | |
| ONLINE = 0x00 | |
| ARMED = 0x01 | |
| def dp_to_power(data: bytes) -> int: | |
| """Convert DP data to power value.""" | |
| # From Koenkk/zigbee2mqtt#18603 (comment) | |
| power = int(data) | |
| if power > 0x0FFFFFFF: | |
| power = (0x1999999C - power) * -1 | |
| return power | |
| def multi_dp_to_power(data: bytes) -> int: | |
| """Convert DP data to power value.""" | |
| # Support negative power readings | |
| # From Koenkk/zigbee2mqtt#18603 (comment) | |
| power = data[7] | (data[6] << 8) | |
| if power > 0x7FFF: | |
| power = (0x999A - power) * -1 | |
| return power | |
| def multi_dp_to_current(data: bytes) -> int: | |
| """Convert DP data to current value.""" | |
| return data[4] | (data[3] << 8) | |
| def multi_dp_to_voltage(data: bytes) -> int: | |
| """Convert DP data to voltage value.""" | |
| return data[1] | (data[0] << 8) | |
| class Tuya1PhaseRCBOEM(ElectricalMeasurement, TuyaLocalCluster): | |
| """Tuya 1 Phase Circuit Breaker with Current Leakage Detection and Electrical Measurement.""" | |
| _CONSTANT_ATTRIBUTES = { | |
| ElectricalMeasurement.AttributeDefs.ac_current_multiplier.id: 1, | |
| ElectricalMeasurement.AttributeDefs.ac_current_divisor.id: 1000, | |
| ElectricalMeasurement.AttributeDefs.ac_voltage_multiplier.id: 1, | |
| ElectricalMeasurement.AttributeDefs.ac_voltage_divisor.id: 10, | |
| ElectricalMeasurement.AttributeDefs.ac_frequency.id: 50, | |
| } | |
| class AttributeDefs(ElectricalMeasurement.AttributeDefs): | |
| """Attribute definitions.""" | |
| def update_attribute(self, attr_name: str, value: Any) -> None: | |
| """Calculate active current and power factor.""" | |
| super().update_attribute(attr_name, value) | |
| if attr_name == "rms_current": | |
| rms_voltage = self.get("rms_voltage") | |
| if rms_voltage: | |
| apparent_power = value * rms_voltage / 1000 / 10 | |
| super().update_attribute("apparent_power", int(apparent_power)) | |
| if attr_name == "active_power": | |
| apparent_power = self.get("apparent_power") | |
| if apparent_power: | |
| power_factor = value / apparent_power * 100 | |
| power_factor = min(power_factor, 100) | |
| super().update_attribute("power_factor", round(power_factor)) | |
| class Tuya1PhaseRCBODT(DeviceTemperature, TuyaLocalCluster): | |
| """Tuya 1 Phase Circuit Breaker with Temperature Sensing Cluster.""" | |
| class AttributeDefs(DeviceTemperature.AttributeDefs): | |
| high_temp_trip = f.ZCLAttributeDef( | |
| id=0xff10, zcl_type=t.Bool, is_manufacturer_specific=True | |
| ) | |
| ( | |
| TuyaQuirkBuilder("_TZE204_fhvdgeuh", "TS0601") | |
| # dp 0x01 - Energy | |
| .tuya_sensor( | |
| dp_id=1, | |
| attribute_name="energy_consumed", | |
| type=t.int32s, | |
| divisor=100, | |
| state_class=SensorStateClass.TOTAL_INCREASING, | |
| device_class=SensorDeviceClass.ENERGY, | |
| unit=UnitOfEnergy.KILO_WATT_HOUR, | |
| fallback_name="Total energy", | |
| ) | |
| # dp 0x06 - Instant Power / Voltage / Current | |
| .tuya_dp_multi( | |
| dp_id=6, | |
| attribute_mapping=[ | |
| DPToAttributeMapping( | |
| ep_attribute=Tuya1PhaseRCBOEM.ep_attribute, | |
| attribute_name=Tuya1PhaseRCBOEM.AttributeDefs.active_power.name, | |
| converter=multi_dp_to_power, | |
| ), | |
| DPToAttributeMapping( | |
| ep_attribute=Tuya1PhaseRCBOEM.ep_attribute, | |
| attribute_name=Tuya1PhaseRCBOEM.AttributeDefs.rms_voltage.name, | |
| converter=multi_dp_to_voltage, | |
| ), | |
| DPToAttributeMapping( | |
| ep_attribute=Tuya1PhaseRCBOEM.ep_attribute, | |
| attribute_name=Tuya1PhaseRCBOEM.AttributeDefs.rms_current.name, | |
| converter=multi_dp_to_current, | |
| ), | |
| ], | |
| ) | |
| # dp 0x09 - Faults | |
| .tuya_enum( | |
| dp_id=9, | |
| attribute_name="fault", | |
| enum_class=FaultCode, | |
| entity_type=EntityType.STANDARD, | |
| entity_platform=EntityPlatform.SENSOR, | |
| translation_key="fault", | |
| fallback_name="Fault", | |
| ) | |
| # dp 0x0b (11) => '?' value Bool.false report every 10m | |
| # dp 0x0c (12) => '?' value Bool.false on boot | |
| # dp 0x0d (13) => '?' value 0 report every 10m | |
| # dp 0x0f - Leakage Current | |
| .tuya_sensor( | |
| dp_id=15, | |
| attribute_name="leakage_current", | |
| type=t.uint32_t, | |
| divisor=1, | |
| state_class=SensorStateClass.MEASUREMENT, | |
| device_class=SensorDeviceClass.CURRENT, | |
| unit=UnitOfElectricCurrent.MILLIAMPERE, | |
| translation_key='leakage_current', | |
| fallback_name="Leakage Current", | |
| ) | |
| # dp 0x10 - On / Off | |
| .tuya_onoff(dp_id=16, onoff_cfg=TuyaRCBOOnOff) | |
| # dp 0x11 (17) - overvoltage / undervoltage thresholds | |
| # dp 0x12 (18) - overcurrent / leakage / temperature? thresholds | |
| # dp 0x66 - Reclose Allowed Times | |
| .tuya_number( | |
| dp_id=102, | |
| type=t.uint16_t, | |
| attribute_name="reclosing_allowed_times", | |
| unit=None, | |
| min_value=1, | |
| max_value=30, | |
| translation_key="reclosing_allowed_times", | |
| fallback_name="Reclose Allowed Times", | |
| mode="box" | |
| ) | |
| # dp 0x67 - Temperature | |
| .tuya_dp( | |
| dp_id=103, | |
| ep_attribute=Tuya1PhaseRCBODT.ep_attribute, | |
| attribute_name=Tuya1PhaseRCBODT.AttributeDefs.current_temperature.name, | |
| converter=lambda x: x * 100 | |
| ) | |
| # dp 0x68 - Reclose Enable | |
| .tuya_switch( | |
| dp_id=104, | |
| attribute_name="reclosing_enable", | |
| translation_key="reclosing_enable", | |
| fallback_name="Reclose Enable", | |
| ) | |
| # dp 0x69 (105) - '?' value 0 report on boot | |
| # dp 0x6a (106) - '?' value 'EwAAAAAAAAAAAA==' ('\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00') report on boot | |
| # dp 0x6b - Reclose Recover Time (s) | |
| .tuya_number( | |
| dp_id=107, | |
| type=t.uint16_t, | |
| attribute_name="reclose_recover_seconds", | |
| unit=UnitOfTime.SECONDS, | |
| min_value=10, | |
| max_value=99, | |
| translation_key="reclose_recover_seconds", | |
| fallback_name="Reclose Recover", | |
| mode="box" | |
| ) | |
| # dp 0x6d (109) - '?' value 'AAAA' ('\x00\x00\x00') report on boot | |
| # dp 0x77 - Power on delay / Power on time (no reporting) | |
| # .tuya_number( | |
| # dp_id=119, | |
| # type=t.uint16_t, | |
| # attribute_name="power_on_delay", | |
| # unit=UnitOfTime.SECONDS, | |
| # min_value=1, | |
| # max_value=50, | |
| # translation_key="power_on_delay", | |
| # fallback_name="Power On Delay", | |
| # mode="box" | |
| # ) | |
| # dp 0x7f - Device Status # need more info on options | |
| .tuya_enum( | |
| dp_id=127, | |
| attribute_name="status", | |
| enum_class=DeviceStatus, | |
| entity_type=EntityType.STANDARD, | |
| entity_platform=EntityPlatform.SENSOR, | |
| translation_key="status", | |
| fallback_name="Status" | |
| ) | |
| # dp 0x86 - Power On State | |
| .tuya_enum( | |
| dp_id=134, | |
| attribute_name="relay_status_for_power_on", | |
| enum_class=PowerOnState, | |
| translation_key="power_on_state", | |
| fallback_name="Power On State" | |
| ) | |
| .adds(Tuya1PhaseRCBOEM) | |
| .adds(Tuya1PhaseRCBODT) | |
| .removes(LevelControl.cluster_id) | |
| .skip_configuration() | |
| .add_to_registry() | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment