zigpy / zha-device-handlers

ZHA device handlers bridge the functionality gap created when manufacturers deviate from the ZCL specification, handling deviations and exceptions by parsing custom messages to and from Zigbee devices.
Apache License 2.0
683 stars 636 forks source link

[Device Support Request] Zemismart Tuya Zigbee 3 Phase Energy Meter SPM02-D2TZ (TS0601 by _TZE200_v9hkz2yn) #3184

Open sltvtr opened 1 month ago

sltvtr commented 1 month ago

Problem description

Hi, I have device SPM02-D2TZ: obraz

Device connects to zigbee network and shows up in HA, but by default it has no entities and no quirk assigned. After adding custom quirk ts0601_din_power.py (file pasted below) with forced model :

class TuyaZemismartPowerMeter(CustomDevice):
[...]
        MODELS_INFO: [
            ("_TZE200_bcusnqt8", "TS0601"),  # SPM01
            ("_TZE204_ves1ycwx", "TS0601"),  # SPM02
            ("_TZE200_ves1ycwx", "TS0601"),  # SPM02
            ("_TZE200_v9hkz2yn", "TS0601"),  # SPM02
        ],

the quirk is assigned and some entities show but only for one phase - there should be 3 phases:

obraz

I'm using current version: Home Assistant Core 2024.5.5 Interfejs użytkownika 20240501.1

Any help/ideas will be appreciated :) Best regards, Janusz

Solution description

The device should be recognized by ZHA without custom quirks and should present data from all 3 phases (not just 1, like now).

Screenshots/Video

Screenshots/Video [Paste/upload your media here]

Device signature

Device signature ```json { "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)", "endpoints": { "1": { "profile_id": "0x0104", "device_type": "0x0051", "input_clusters": [ "0x0000", "0x0004", "0x0005", "0x0702", "0x0b04", "0xef00" ], "output_clusters": [ "0x000a", "0x0019" ] } }, "manufacturer": "_TZE200_v9hkz2yn", "model": "TS0601", "class": "ts0601_din_power.TuyaZemismartPowerMeter" } ```

Diagnostic information

Diagnostic information ```json { "home_assistant": { "installation_type": "Home Assistant Container", "version": "2024.5.5", "dev": false, "hassio": false, "virtualenv": false, "python_version": "3.12.2", "docker": true, "arch": "x86_64", "timezone": "Europe/Warsaw", "os_name": "Linux", "os_version": "5.15.0-92-generic", "run_as_root": true }, "custom_components": { "hacs": { "documentation": "https://hacs.xyz/docs/configuration/start", "version": "1.34.0", "requirements": [ "aiogithubapi>=22.10.1" ] }, "zha_toolkit": { "documentation": "https://github.com/mdeweerd/zha-toolkit", "version": "v1.1.10", "requirements": [ "pytz" ] }, "smartthinq_sensors": { "documentation": "https://github.com/ollo69/ha-smartthinq-sensors", "version": "0.39.1", "requirements": [ "pycountry>=23.12.11", "xmltodict>=0.13.0", "charset_normalizer>=3.2.0" ] } }, "integration_manifest": { "domain": "zha", "name": "Zigbee Home Automation", "after_dependencies": [ "onboarding", "usb" ], "codeowners": [ "@dmulcahey", "@adminiuga", "@puddly", "@TheJulianJES" ], "config_flow": true, "dependencies": [ "file_upload" ], "documentation": "https://www.home-assistant.io/integrations/zha", "iot_class": "local_polling", "loggers": [ "aiosqlite", "bellows", "crccheck", "pure_pcapy3", "zhaquirks", "zigpy", "zigpy_deconz", "zigpy_xbee", "zigpy_zigate", "zigpy_znp", "universal_silabs_flasher" ], "requirements": [ "bellows==0.38.4", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.115", "zigpy-deconz==0.23.1", "zigpy==0.64.0", "zigpy-xbee==0.20.1", "zigpy-zigate==0.12.0", "zigpy-znp==0.12.1", "universal-silabs-flasher==0.0.18", "pyserial-asyncio-fast==0.11" ], "usb": [ { "vid": "10C4", "pid": "EA60", "description": "*2652*", "known_devices": [ "slae.sh cc2652rb stick" ] }, { "vid": "10C4", "pid": "EA60", "description": "*slzb-07*", "known_devices": [ "smlight slzb-07" ] }, { "vid": "1A86", "pid": "55D4", "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus v2" ] }, { "vid": "10C4", "pid": "EA60", "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus" ] }, { "vid": "10C4", "pid": "EA60", "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ] }, { "vid": "1A86", "pid": "7523", "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ] }, { "vid": "1A86", "pid": "7523", "description": "*zigstar*", "known_devices": [ "ZigStar Coordinators" ] }, { "vid": "1CF1", "pid": "0030", "description": "*conbee*", "known_devices": [ "Conbee II" ] }, { "vid": "0403", "pid": "6015", "description": "*conbee*", "known_devices": [ "Conbee III" ] }, { "vid": "10C4", "pid": "8A2A", "description": "*zigbee*", "known_devices": [ "Nortek HUSBZB-1" ] }, { "vid": "0403", "pid": "6015", "description": "*zigate*", "known_devices": [ "ZiGate+" ] }, { "vid": "10C4", "pid": "EA60", "description": "*zigate*", "known_devices": [ "ZiGate" ] }, { "vid": "10C4", "pid": "8B34", "description": "*bv 2010/10*", "known_devices": [ "Bitron Video AV2010/10" ] } ], "zeroconf": [ { "type": "_esphomelib._tcp.local.", "name": "tube*" }, { "type": "_zigate-zigbee-gateway._tcp.local.", "name": "*zigate*" }, { "type": "_zigstar_gw._tcp.local.", "name": "*zigstar*" }, { "type": "_uzg-01._tcp.local.", "name": "uzg-01*" }, { "type": "_slzb-06._tcp.local.", "name": "slzb-06*" } ], "is_built_in": true }, "data": { "ieee": "**REDACTED**", "nwk": 4546, "manufacturer": "_TZE200_v9hkz2yn", "model": "TS0601", "name": "_TZE200_v9hkz2yn TS0601", "quirk_applied": true, "quirk_class": "ts0601_zemismart_power_meter.TuyaZemismartPowerMeter", "quirk_id": null, "manufacturer_code": 4098, "power_source": "Mains", "lqi": 184, "rssi": -54, "last_seen": "2024-06-03T09:29:22", "available": true, "device_type": "Router", "signature": { "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)", "endpoints": { "1": { "profile_id": "0x0104", "device_type": "0x0051", "input_clusters": [ "0x0000", "0x0004", "0x0005", "0x0702", "0x0b04", "0xef00" ], "output_clusters": [ "0x000a", "0x0019" ] } }, "manufacturer": "_TZE200_v9hkz2yn", "model": "TS0601" }, "active_coordinator": false, "entities": [ { "entity_id": "sensor.licznik_natezenie_pradu", "name": "_TZE200_v9hkz2yn TS0601" }, { "entity_id": "sensor.licznik_napiecie", "name": "_TZE200_v9hkz2yn TS0601" }, { "entity_id": "sensor.licznik_summation_received", "name": "_TZE200_v9hkz2yn TS0601" }, { "entity_id": "sensor.licznik_moc", "name": "_TZE200_v9hkz2yn TS0601" }, { "entity_id": "sensor.licznik_suma_dostarczonej_energii", "name": "_TZE200_v9hkz2yn TS0601" }, { "entity_id": "update.licznik_firmware", "name": "_TZE200_v9hkz2yn TS0601" } ], "neighbors": [ { "device_type": "Coordinator", "rx_on_when_idle": "On", "relationship": "Parent", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x0000", "permit_joining": "Unknown", "depth": "0", "lqi": "156" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x1F9F", "permit_joining": "Unknown", "depth": "15", "lqi": "70" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x2956", "permit_joining": "Unknown", "depth": "15", "lqi": "182" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x2ABB", "permit_joining": "Unknown", "depth": "15", "lqi": "85" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x31F7", "permit_joining": "Unknown", "depth": "15", "lqi": "93" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x35FF", "permit_joining": "Unknown", "depth": "15", "lqi": "95" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x3E3C", "permit_joining": "Unknown", "depth": "15", "lqi": "61" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x8341", "permit_joining": "Unknown", "depth": "15", "lqi": "106" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x8CA7", "permit_joining": "Unknown", "depth": "15", "lqi": "91" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x9941", "permit_joining": "Unknown", "depth": "15", "lqi": "91" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x9954", "permit_joining": "Unknown", "depth": "15", "lqi": "102" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xCEC6", "permit_joining": "Unknown", "depth": "15", "lqi": "55" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xD677", "permit_joining": "Unknown", "depth": "15", "lqi": "42" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xE436", "permit_joining": "Unknown", "depth": "15", "lqi": "157" }, { "device_type": "EndDevice", "rx_on_when_idle": "Off", "relationship": "Child", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xEC74", "permit_joining": "NotAccepting", "depth": "2", "lqi": "161" } ], "routes": [ { "dest_nwk": "0x0000", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0x0000" }, { "dest_nwk": "0x2956", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0x2956" }, { "dest_nwk": "0xE8D1", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0x8341" }, { "dest_nwk": "0x31F7", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0x8341" }, { "dest_nwk": "0xDF41", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0x8341" }, { "dest_nwk": "0xF2C4", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0x8341" }, { "dest_nwk": "0x9954", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0xE436" }, { "dest_nwk": "0x7713", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0x8341" } ], "endpoint_names": [ { "name": "SMART_PLUG" } ], "user_given_name": "Licznik", "device_reg_id": "f80767b2957b4d07395a9555c64f2693", "area_id": "garaz", "cluster_details": { "1": { "device_type": { "name": "SMART_PLUG", "id": 81 }, "profile_id": 260, "in_clusters": { "0x0000": { "endpoint_attribute": "basic", "attributes": { "0x0001": { "attribute_name": "app_version", "value": 68 }, "0x0004": { "attribute_name": "manufacturer", "value": "_TZE200_v9hkz2yn" }, "0x0005": { "attribute_name": "model", "value": "TS0601" } }, "unsupported_attributes": {} }, "0x0004": { "endpoint_attribute": "groups", "attributes": {}, "unsupported_attributes": {} }, "0x0005": { "endpoint_attribute": "scenes", "attributes": {}, "unsupported_attributes": {} }, "0xef00": { "endpoint_attribute": "tuya_manufacturer", "attributes": { "0x0201": { "attribute_name": "energy", "value": 2164 }, "0x0202": { "attribute_name": "reverse_energy", "value": 0 }, "0x0006": { "attribute_name": "vcp_raw", "value": [ 35, 0, 0, 112, 1, 0, 127, 9 ] } }, "unsupported_attributes": {} }, "0x0702": { "endpoint_attribute": "smartenergy_metering", "attributes": { "0x0000": { "attribute_name": "current_summ_delivered", "value": 2164 }, "0x0001": { "attribute_name": "current_summ_received", "value": 0 }, "0x0302": { "attribute_name": "divisor", "value": 1000 }, "0x0300": { "attribute_name": "unit_of_measure", "value": 0 } }, "unsupported_attributes": { "0x0301": { "attribute_name": "multiplier" }, "0x0303": { "attribute_name": "summation_formatting" }, "0x0304": { "attribute_name": "demand_formatting" }, "0x0306": { "attribute_name": "metering_device_type" } } }, "0x0b04": { "endpoint_attribute": "electrical_measurement", "attributes": { "0x0603": { "attribute_name": "ac_current_divisor", "value": 1000 }, "0x0602": { "attribute_name": "ac_current_multiplier", "value": 1 }, "0x0601": { "attribute_name": "ac_voltage_divisor", "value": 10 }, "0x0600": { "attribute_name": "ac_voltage_multiplier", "value": 1 }, "0x050b": { "attribute_name": "active_power", "value": 35 }, "0x090b": { "attribute_name": "active_power_ph_b", "value": 42 }, "0x0a0b": { "attribute_name": "active_power_ph_c", "value": 26 }, "0x0508": { "attribute_name": "rms_current", "value": 368 }, "0x0908": { "attribute_name": "rms_current_ph_b", "value": 758 }, "0x0a08": { "attribute_name": "rms_current_ph_c", "value": 301 }, "0x0505": { "attribute_name": "rms_voltage", "value": 2431 }, "0x0905": { "attribute_name": "rms_voltage_ph_b", "value": 2416 }, "0x0a05": { "attribute_name": "rms_voltage_ph_c", "value": 2417 } }, "unsupported_attributes": { "0x0400": { "attribute_name": "ac_frequency_multiplier" }, "0x0401": { "attribute_name": "ac_frequency_divisor" }, "0x0000": { "attribute_name": "measurement_type" }, "0x0403": { "attribute_name": "power_divisor" }, "0x0604": { "attribute_name": "ac_power_multiplier" }, "0x0605": { "attribute_name": "ac_power_divisor" }, "0x0402": { "attribute_name": "power_multiplier" }, "0x0302": { "attribute_name": "ac_frequency_max" }, "0x0300": { "attribute_name": "ac_frequency" }, "0x0507": { "attribute_name": "rms_voltage_max" }, "0x050a": { "attribute_name": "rms_current_max" }, "0x050d": { "attribute_name": "active_power_max" }, "0x050f": { "attribute_name": "apparent_power" }, "0x0510": { "attribute_name": "power_factor" } } } }, "out_clusters": { "0x000a": { "endpoint_attribute": "time", "attributes": {}, "unsupported_attributes": {} }, "0x0019": { "endpoint_attribute": "ota", "attributes": { "0x0002": { "attribute_name": "current_file_version", "value": 68 } }, "unsupported_attributes": { "0x0002": { "attribute_name": "current_file_version" } } } } } } } } ```

Logs

Logs ```python [Paste the logs here] ```

Custom quirk

Custom quirk ```python """Tuya Din Power Meter.""" from zigpy.profiles import zha from zigpy.quirks import CustomDevice import zigpy.types as t from zigpy.zcl.clusters.general import Basic, Groups, Ota, Scenes, Time from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement from zigpy.zcl.clusters.smartenergy import Metering from zhaquirks import Bus, LocalDataCluster from zhaquirks.const import ( DEVICE_TYPE, ENDPOINTS, INPUT_CLUSTERS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, ) from zhaquirks.tuya import TuyaManufClusterAttributes, TuyaOnOff, TuyaSwitch TUYA_TOTAL_ENERGY_ATTR = 0x0211 TUYA_CURRENT_ATTR = 0x0212 TUYA_POWER_ATTR = 0x0213 TUYA_VOLTAGE_ATTR = 0x0214 TUYA_DIN_SWITCH_ATTR = 0x0101 SWITCH_EVENT = "switch_event" """Hiking Power Meter Attributes""" HIKING_DIN_SWITCH_ATTR = 0x0110 HIKING_TOTAL_ENERGY_DELIVERED_ATTR = 0x0201 HIKING_TOTAL_ENERGY_RECEIVED_ATTR = 0x0266 HIKING_VOLTAGE_CURRENT_ATTR = 0x0006 HIKING_POWER_ATTR = 0x0267 HIKING_FREQUENCY_ATTR = 0x0269 HIKING_POWER_FACTOR_ATTR = 0x026F HIKING_TOTAL_REACTIVE_ATTR = 0x026D HIKING_REACTIVE_POWER_ATTR = 0x026E """Zemismart Power Meter Attributes""" ZEMISMART_TOTAL_ENERGY_ATTR = 0x0201 ZEMISMART_TOTAL_REVERSE_ENERGY_ATTR = 0x0202 ZEMISMART_VCP_ATTR = 0x0006 ZEMISMART_VCP_P2_ATTR = ZEMISMART_VCP_ATTR + 1 ZEMISMART_VCP_P3_ATTR = ZEMISMART_VCP_ATTR + 2 class TuyaManufClusterDinPower(TuyaManufClusterAttributes): """Manufacturer Specific Cluster of the Tuya Power Meter device.""" attributes = { TUYA_TOTAL_ENERGY_ATTR: ("energy", t.uint32_t, True), TUYA_CURRENT_ATTR: ("current", t.int16s, True), TUYA_POWER_ATTR: ("power", t.uint16_t, True), TUYA_VOLTAGE_ATTR: ("voltage", t.uint16_t, True), TUYA_DIN_SWITCH_ATTR: ("switch", t.uint8_t, True), } def _update_attribute(self, attrid, value): super()._update_attribute(attrid, value) if attrid == TUYA_TOTAL_ENERGY_ATTR: self.endpoint.smartenergy_metering.energy_deliver_reported(value / 100) elif attrid == TUYA_CURRENT_ATTR: self.endpoint.electrical_measurement.current_reported(value) elif attrid == TUYA_POWER_ATTR: self.endpoint.electrical_measurement.power_reported(value / 10) elif attrid == TUYA_VOLTAGE_ATTR: self.endpoint.electrical_measurement.voltage_reported(value / 10) elif attrid == TUYA_DIN_SWITCH_ATTR: self.endpoint.device.switch_bus.listener_event( SWITCH_EVENT, self.endpoint.endpoint_id, value ) class TuyaPowerMeasurement(LocalDataCluster, ElectricalMeasurement): """Custom class for power, voltage and current measurement.""" cluster_id = ElectricalMeasurement.cluster_id POWER_ID = 0x050B VOLTAGE_ID = 0x0505 CURRENT_ID = 0x0508 REACTIVE_POWER_ID = 0x050E AC_FREQUENCY_ID = 0x0300 TOTAL_REACTIVE_POWER_ID = 0x0305 POWER_FACTOR_ID = 0x0510 AC_CURRENT_MULTIPLIER = 0x0602 AC_CURRENT_DIVISOR = 0x0603 AC_FREQUENCY_MULTIPLIER = 0x0400 AC_FREQUENCY_DIVISOR = 0x0401 _CONSTANT_ATTRIBUTES = { AC_CURRENT_MULTIPLIER: 1, AC_CURRENT_DIVISOR: 1000, AC_FREQUENCY_MULTIPLIER: 1, AC_FREQUENCY_DIVISOR: 100, } def voltage_reported(self, value): """Voltage reported.""" self._update_attribute(self.VOLTAGE_ID, value) def power_reported(self, value): """Power reported.""" self._update_attribute(self.POWER_ID, value) def power_factor_reported(self, value): """Power Factor reported.""" self._update_attribute(self.POWER_FACTOR_ID, value) def reactive_power_reported(self, value): """Reactive Power reported.""" self._update_attribute(self.REACTIVE_POWER_ID, value) def current_reported(self, value): """Ampers reported.""" self._update_attribute(self.CURRENT_ID, value) def frequency_reported(self, value): """AC Frequency reported.""" self._update_attribute(self.AC_FREQUENCY_ID, value) def reactive_energy_reported(self, value): """Summation Reactive Energy reported.""" self._update_attribute(self.TOTAL_REACTIVE_POWER_ID, value) class TuyaElectricalMeasurement(LocalDataCluster, Metering): """Custom class for total energy measurement.""" cluster_id = Metering.cluster_id CURRENT_DELIVERED_ID = 0x0000 CURRENT_RECEIVED_ID = 0x0001 POWER_WATT = 0x0000 """Setting unit of measurement.""" _CONSTANT_ATTRIBUTES = {0x0300: POWER_WATT} def energy_deliver_reported(self, value): """Summation Energy Deliver reported.""" self._update_attribute(self.CURRENT_DELIVERED_ID, value) def energy_receive_reported(self, value): """Summation Energy Receive reported.""" self._update_attribute(self.CURRENT_RECEIVED_ID, value) class HikingManufClusterDinPower(TuyaManufClusterAttributes): """Manufacturer Specific Cluster of the Hiking Power Meter device.""" attributes = { HIKING_DIN_SWITCH_ATTR: ("switch", t.uint8_t, True), HIKING_TOTAL_ENERGY_DELIVERED_ATTR: ("energy_delivered", t.uint32_t, True), HIKING_TOTAL_ENERGY_RECEIVED_ATTR: ("energy_received", t.uint16_t, True), HIKING_VOLTAGE_CURRENT_ATTR: ("voltage_current", t.uint32_t, True), HIKING_POWER_ATTR: ("power", t.uint16_t, True), HIKING_FREQUENCY_ATTR: ("frequency", t.uint16_t, True), HIKING_TOTAL_REACTIVE_ATTR: ("total_reactive_energy", t.int32s, True), HIKING_REACTIVE_POWER_ATTR: ("reactive_power", t.int16s, True), HIKING_POWER_FACTOR_ATTR: ("power_factor", t.uint16_t, True), } def _update_attribute(self, attrid, value): super()._update_attribute(attrid, value) if attrid == HIKING_DIN_SWITCH_ATTR: self.endpoint.device.switch_bus.listener_event(SWITCH_EVENT, 16, value) elif attrid == HIKING_TOTAL_ENERGY_DELIVERED_ATTR: self.endpoint.smartenergy_metering.energy_deliver_reported(value / 100) elif attrid == HIKING_TOTAL_ENERGY_RECEIVED_ATTR: self.endpoint.smartenergy_metering.energy_receive_reported(value / 100) elif attrid == HIKING_VOLTAGE_CURRENT_ATTR: self.endpoint.electrical_measurement.current_reported(value >> 16) self.endpoint.electrical_measurement.voltage_reported( (value & 0x0000FFFF) / 10 ) elif attrid == HIKING_POWER_ATTR: self.endpoint.electrical_measurement.power_reported(value) elif attrid == HIKING_FREQUENCY_ATTR: self.endpoint.electrical_measurement.frequency_reported(value) elif attrid == HIKING_TOTAL_REACTIVE_ATTR: self.endpoint.electrical_measurement.reactive_energy_reported(value) elif attrid == HIKING_REACTIVE_POWER_ATTR: self.endpoint.electrical_measurement.reactive_power_reported(value) elif attrid == HIKING_POWER_FACTOR_ATTR: self.endpoint.electrical_measurement.power_factor_reported(value / 10) class ZemismartManufCluster(TuyaManufClusterAttributes): """Manufacturer Specific Cluster of the Zemismart SPM series Power Meter devices.""" attributes = { ZEMISMART_TOTAL_ENERGY_ATTR: ("energy", t.uint32_t, True), ZEMISMART_TOTAL_REVERSE_ENERGY_ATTR: ("reverse_energy", t.uint32_t, True), ZEMISMART_VCP_ATTR: ("vcp_raw", t.data64, True), ZEMISMART_VCP_P2_ATTR: ("vcp_p2_raw", t.data64, True), ZEMISMART_VCP_P3_ATTR: ("vcp_p3_raw", t.data64, True), } def _update_attribute(self, attrid, value): super()._update_attribute(attrid, value) if attrid == ZEMISMART_TOTAL_ENERGY_ATTR: self.endpoint.smartenergy_metering.energy_deliver_reported(value) elif attrid == ZEMISMART_TOTAL_REVERSE_ENERGY_ATTR: self.endpoint.smartenergy_metering.energy_receive_reported(value) elif attrid == ZEMISMART_VCP_ATTR: self.endpoint.electrical_measurement.vcp_reported(value, 0) elif attrid == ZEMISMART_VCP_P2_ATTR: self.endpoint.electrical_measurement.vcp_reported(value, 1) elif attrid == ZEMISMART_VCP_P3_ATTR: self.endpoint.electrical_measurement.vcp_reported(value, 2) class ZemismartPowerMeasurement(LocalDataCluster, ElectricalMeasurement): """Custom class for power, voltage and current measurement.""" """Setting unit of measurement.""" _CONSTANT_ATTRIBUTES = { ElectricalMeasurement.AttributeDefs.ac_voltage_multiplier.id: 1, ElectricalMeasurement.AttributeDefs.ac_voltage_divisor.id: 10, ElectricalMeasurement.AttributeDefs.ac_current_multiplier.id: 1, ElectricalMeasurement.AttributeDefs.ac_current_divisor.id: 1000, } phase_attributes = [ { # Phase 1 (X) "voltage": ElectricalMeasurement.AttributeDefs.rms_voltage.id, "current": ElectricalMeasurement.AttributeDefs.rms_current.id, "power": ElectricalMeasurement.AttributeDefs.active_power.id, }, { # Phase 2 (Y) "voltage": ElectricalMeasurement.AttributeDefs.rms_voltage_ph_b.id, "current": ElectricalMeasurement.AttributeDefs.rms_current_ph_b.id, "power": ElectricalMeasurement.AttributeDefs.active_power_ph_b.id, }, { # Phase 3 (Z) "voltage": ElectricalMeasurement.AttributeDefs.rms_voltage_ph_c.id, "current": ElectricalMeasurement.AttributeDefs.rms_current_ph_c.id, "power": ElectricalMeasurement.AttributeDefs.active_power_ph_c.id, }, ] # Voltage, current, power is delivered in one value def vcp_reported(self, value, phase=0): """Voltage, current, power reported.""" if phase < 0 or phase > 2: phase = 0 voltage = int.from_bytes(value[6:8], byteorder="little") current = int.from_bytes(value[3:6], byteorder="little") power = int.from_bytes(value[0:3], byteorder="little") self._update_attribute(self.phase_attributes[phase]["voltage"], voltage) self._update_attribute(self.phase_attributes[phase]["current"], current) self._update_attribute(self.phase_attributes[phase]["power"], power) class ZemismartElectricalMeasurement(TuyaElectricalMeasurement): """Custom class for total energy measurement.""" """Setting unit of measurement.""" _CONSTANT_ATTRIBUTES = { Metering.AttributeDefs.unit_of_measure.id: 0, # kWh Metering.AttributeDefs.divisor.id: 100, } class TuyaPowerMeter(TuyaSwitch): """Tuya power meter device.""" def __init__(self, *args, **kwargs): """Init device.""" self.switch_bus = Bus() super().__init__(*args, **kwargs) signature = { # "node_descriptor": "", # device_version=1 # input_clusters=[0x0000, 0x0004, 0x0005, 0xef00] # output_clusters=[0x000a, 0x0019] MODELS_INFO: [ ("_TZE200_byzdayie", "TS0601"), ("_TZE200_ewxhg6o9", "TS0601"), ], ENDPOINTS: { # 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.SMART_PLUG, INPUT_CLUSTERS: [ Basic.cluster_id, Groups.cluster_id, Scenes.cluster_id, TuyaManufClusterAttributes.cluster_id, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], } }, } replacement = { ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.SMART_PLUG, INPUT_CLUSTERS: [ Basic.cluster_id, Groups.cluster_id, Scenes.cluster_id, TuyaManufClusterDinPower, TuyaPowerMeasurement, TuyaElectricalMeasurement, TuyaOnOff, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], } } } class HikingPowerMeter(TuyaSwitch): """Hiking Power Meter Device - DDS238-2.""" signature = { # "node_descriptor": "", # device_version=1 # input_clusters=[0x0000, 0x0004, 0x0005, 0xef00] # output_clusters=[0x000a, 0x0019] MODELS_INFO: [("_TZE200_bkkmqmyo", "TS0601")], ENDPOINTS: { # 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.SMART_PLUG, INPUT_CLUSTERS: [ Basic.cluster_id, Groups.cluster_id, Scenes.cluster_id, TuyaManufClusterAttributes.cluster_id, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], } }, } replacement = { ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.SMART_PLUG, INPUT_CLUSTERS: [ Basic.cluster_id, Groups.cluster_id, Scenes.cluster_id, HikingManufClusterDinPower, TuyaElectricalMeasurement, TuyaPowerMeasurement, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], }, 16: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.SMART_PLUG, INPUT_CLUSTERS: [ TuyaOnOff, ], OUTPUT_CLUSTERS: [], }, } } class TuyaZemismartPowerMeter(CustomDevice): """Zemismart power meter device.""" signature = { # "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, # user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, # mac_capability_flags=, # manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, # maximum_outgoing_transfer_size=82, descriptor_capability_field=, # *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, # *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, # *is_security_capable=False)", # device_version=1 # input_clusters=[0x0000, 0x0004, 0x0005, 0xef00] # output_clusters=[0x000a, 0x0019] MODELS_INFO: [ ("_TZE200_bcusnqt8", "TS0601"), # SPM01 ("_TZE204_ves1ycwx", "TS0601"), # SPM02 ("_TZE200_ves1ycwx", "TS0601"), # SPM02 ("_TZE200_v9hkz2yn", "TS0601"), # SPM02 ], ENDPOINTS: { # 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.SMART_PLUG, INPUT_CLUSTERS: [ Basic.cluster_id, Groups.cluster_id, Scenes.cluster_id, TuyaManufClusterAttributes.cluster_id, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], } }, } replacement = { ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.SMART_PLUG, INPUT_CLUSTERS: [ Basic.cluster_id, Groups.cluster_id, Scenes.cluster_id, ZemismartManufCluster, ZemismartElectricalMeasurement, ZemismartPowerMeasurement, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], } } } ```

Additional information

No response

sltvtr commented 1 month ago

One additional information - this device works without any issue by default under zigbee2mqtt, I've just checked it:

obraz

obraz

obraz

https://www.zigbee2mqtt.io/devices/SPM02V2.html

rrataj commented 2 weeks ago

Hey There! I created a quirk for _TZE200_v9hkz2yn version which supports 3 phase Voltage, Current and Power stats: https://github.com/rrataj/zha-device-handlers/blob/0a6b19ab591378c53f6a8887e6c65b76ea31084b/zhaquirks/tuya/ts0601_din_power_3.py

Hope that helps someone :)

sltvtr commented 2 weeks ago

Hi! Thank You for the contribution, it looks really better now, and after some quick testing I can say it reports independent values from 3 phases. What bothers me - there are four measurments for power, current and voltage, and I think something is off. I think the first measurment of the same type should be sum (or maybe average for voltage), like: PowerABC PowerA PowerB PowerC but I see that always PowerABC = PowerA, the same for Current and Voltage:

obraz

Am I right?

Thanks again for Your work, it looks promising :-) Best regards.

rrataj commented 2 weeks ago

Yes, you are right, there are 4 groups (ABC, A, B, C), where currently 1st group (ABC) is the same as 1st phase (A). Maybe I will find time and adapt it to show correct sum, but for now I do this directly in HA via simple sum:

{{ (states('sensor.miernik_energii_moc_2')|float(2) + states('sensor.miernik_energii_moc_3')|float(2) + states('sensor.miernik_energii_moc_4')|float(2) )| round(0) }}

Zrzut ekranu 2024-06-19 o 14 57 01 Zrzut ekranu 2024-06-19 o 14 57 22

sltvtr commented 2 weeks ago

Thanks, this is a very nice workaround and it works like a charm ;-) I can definitely live with that, until official resolution comes into place :)

juapem commented 3 days ago

It worked