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
682 stars 633 forks source link

[Device Support Request] Earu EAYCBM-Z #2061

Open pkejval opened 1 year ago

pkejval commented 1 year ago

Device: Earu EAYCBM-Z 32A bought at: https://www.aliexpress.com/i/1005004894129906.html

Device didn't expose any attribute nor switch in ZHA integration. Tried some custom quirks for din rail power meters TS0601 but none is working. :(

Device signature ```yaml { "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": 260, "device_type": "0x0051", "in_clusters": [ "0x0000", "0x0004", "0x0005", "0xef00" ], "out_clusters": [ "0x000a", "0x0019" ] } }, "manufacturer": "_TZE204_davzgqq0", "model": "TS0601", "class": "zigpy.device.Device" } ```
Diagnostic information ```yaml { "home_assistant": { "installation_type": "Home Assistant OS", "version": "2022.12.9", "dev": false, "hassio": true, "virtualenv": false, "python_version": "3.10.7", "docker": true, "arch": "x86_64", "timezone": "Europe/Prague", "os_name": "Linux", "os_version": "5.15.80", "supervisor": "2022.12.1", "host_os": "Home Assistant OS 9.4", "docker_version": "20.10.19", "chassis": "vm", "run_as_root": true }, "custom_components": { "nodered": { "version": "1.1.2", "requirements": [] }, "weatherbit": { "version": "1.0.17", "requirements": [ "pyweatherbitdata==1.0.15" ] }, "ble_monitor": { "version": "11.2.0", "requirements": [ "pycryptodomex>=3.14.1", "janus>=1.0.0", "aioblescan>=0.2.13", "btsocket>=0.2.0", "pyric>=0.1.6.3" ] }, "remote_homeassistant": { "version": "3.6", "requirements": [] }, "tuya": { "version": "1.6.0+git20211001", "requirements": [ "tuya-iot-py-sdk==0.6.3" ] }, "hacs": { "version": "1.29.0", "requirements": [ "aiogithubapi>=22.10.1" ] }, "pyscript": { "version": "1.3.3", "requirements": [ "croniter==1.3.4", "watchdog==2.1.6" ] }, "met_next_6_hours_forecast": { "version": "1.0.3", "requirements": [] }, "moonraker": { "version": "2021.10.0", "requirements": [ "moonraker-api==2.0.4" ] }, "localtuya": { "version": "4.1.1", "requirements": [] }, "gree": { "version": "2.3.1", "requirements": [ "pycryptodome" ] }, "arpscan_tracker": { "version": "1.0.6", "requirements": [] }, "zha_toolkit": { "version": "v0.8.28", "requirements": [ "packaging>=20.8", "pytz" ] }, "garbage_collection": { "version": "4.10.2", "requirements": [ "python-dateutil>=2.8.2" ] }, "xiaomi_miot": { "version": "0.7.4", "requirements": [ "construct==2.10.56", "python-miio>=0.5.6", "micloud>=0.3" ] }, "xiaomi_cloud_map_extractor": { "version": "v2.2.0", "requirements": [ "pillow", "pybase64", "python-miio", "requests", "pycryptodome" ] } }, "integration_manifest": { "domain": "zha", "name": "Zigbee Home Automation", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ "bellows==0.34.5", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.89", "zigpy-deconz==0.19.2", "zigpy==0.52.3", "zigpy-xbee==0.16.2", "zigpy-zigate==0.10.3", "zigpy-znp==0.9.2" ], "usb": [ { "vid": "10C4", "pid": "EA60", "description": "*2652*", "known_devices": [ "slae.sh cc2652rb stick" ] }, { "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": "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" ] } ], "codeowners": [ "@dmulcahey", "@adminiuga", "@puddly" ], "zeroconf": [ { "type": "_esphomelib._tcp.local.", "name": "tube*" }, { "type": "_zigate-zigbee-gateway._tcp.local.", "name": "*zigate*" }, { "type": "_zigstar_gw._tcp.local.", "name": "*zigstar*" } ], "dependencies": [ "file_upload" ], "after_dependencies": [ "onboarding", "usb", "zeroconf" ], "iot_class": "local_polling", "loggers": [ "aiosqlite", "bellows", "crccheck", "pure_pcapy3", "zhaquirks", "zigpy", "zigpy_deconz", "zigpy_xbee", "zigpy_zigate", "zigpy_znp" ], "is_built_in": true }, "data": { "ieee": "**REDACTED**", "nwk": 6209, "manufacturer": "_TZE204_davzgqq0", "model": "TS0601", "name": "_TZE204_davzgqq0 TS0601", "quirk_applied": false, "quirk_class": "zigpy.device.Device", "manufacturer_code": 4098, "power_source": "Mains", "lqi": 92, "rssi": -77, "last_seen": "2023-01-03T18:23:03", "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": 260, "device_type": "0x0051", "in_clusters": [ "0x0000", "0x0004", "0x0005", "0xef00" ], "out_clusters": [ "0x000a", "0x0019" ] } } }, "active_coordinator": false, "entities": [], "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": "108" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x2214", "permit_joining": "Unknown", "depth": "15", "lqi": "226" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x2A97", "permit_joining": "Unknown", "depth": "15", "lqi": "255" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x76E5", "permit_joining": "Unknown", "depth": "15", "lqi": "73" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x8441", "permit_joining": "Unknown", "depth": "15", "lqi": "89" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xCA9B", "permit_joining": "Unknown", "depth": "15", "lqi": "255" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xEC35", "permit_joining": "Unknown", "depth": "15", "lqi": "202" }, { "device_type": "EndDevice", "rx_on_when_idle": "Off", "relationship": "Child", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x9A11", "permit_joining": "NotAccepting", "depth": "2", "lqi": "152" } ], "routes": [ { "dest_nwk": "0x0000", "route_status": "Active", "memory_constrained": false, "many_to_one": true, "route_record_required": true, "next_hop": "0x0000" }, { "dest_nwk": "0x2214", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0x2214" }, { "dest_nwk": "0x76E5", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0xEC35" }, { "dest_nwk": "0x8441", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0xEC35" }, { "dest_nwk": "0xCA9B", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0xCA9B" }, { "dest_nwk": "0x9808", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0xEC35" }, { "dest_nwk": "0x0443", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0xEC35" }, { "dest_nwk": "0x2E70", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0xEC35" }, { "dest_nwk": "0xEC35", "route_status": "Active", "memory_constrained": false, "many_to_one": false, "route_record_required": false, "next_hop": "0xEC35" } ], "endpoint_names": [ { "name": "SMART_PLUG" } ], "user_given_name": null, "device_reg_id": "fcd037d74543544455a295c549a2ac7f", "area_id": "chodba", "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": 70 }, "0x0004": { "attribute_name": "manufacturer", "value": "_TZE204_davzgqq0" }, "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": null, "attributes": {}, "unsupported_attributes": {} } }, "out_clusters": { "0x0019": { "endpoint_attribute": "ota", "attributes": {}, "unsupported_attributes": {} }, "0x000a": { "endpoint_attribute": "time", "attributes": {}, "unsupported_attributes": {} } } } } } } ```
TheJulianJES commented 1 year ago

Might not work, but I guess it's worth a try (put this in a file and try it as a custom quirk): (After having restarted HA with the quirk, also try to remove and re-pair the device)

Possible quirk code ```python """Quirk for _TZE204_davzgqq0 TS0601.""" from zigpy.profiles import zha from zigpy.quirks import CustomDevice from zigpy.zcl.clusters.general import ( Basic, Groups, Ota, Scenes, Time, ) from zhaquirks.const import ( DEVICE_TYPE, ENDPOINTS, INPUT_CLUSTERS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, ) from zhaquirks.tuya import TuyaSwitch from zhaquirks.tuya.mcu import ( MoesSwitchManufCluster, TuyaOnOff, TuyaOnOffManufCluster, ) class Tze204Davzgqq0Ts0601(CustomDevice): """_Tze204_Davzgqq0 TS0601 custom device implementation.""" signature = { MODELS_INFO: [("_TZE204_davzgqq0", "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, TuyaOnOffManufCluster.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, MoesSwitchManufCluster, TuyaOnOff, ], OUTPUT_CLUSTERS: [ Time.cluster_id, Ota.cluster_id, ] } } } ```
pkejval commented 1 year ago

It's unfortunately not working. Just exposes switch which is still in on state in HA regardless actual state of device and clicking it does nothing.

image

TheJulianJES commented 1 year ago

Hmm. Try replacing both occurrences of TuyaOnOff with TuyaOnOffNM and see if that makes a difference. (One is in the imports and the other one is in replacement/ENDPOINTS/1/INPUT_CLUSTERS)

pkejval commented 1 year ago

Still the same.

image

Quirk code ```python """Quirk for _TZE204_davzgqq0 TS0601.""" from zigpy.profiles import zha from zigpy.quirks import CustomDevice from zigpy.zcl.clusters.general import ( Basic, Groups, Ota, Scenes, Time, ) from zhaquirks.const import ( DEVICE_TYPE, ENDPOINTS, INPUT_CLUSTERS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, ) from zhaquirks.tuya import TuyaSwitch from zhaquirks.tuya.mcu import ( MoesSwitchManufCluster, TuyaOnOffNM, TuyaOnOffManufCluster, ) class Tze204Davzgqq0Ts0601(CustomDevice): """_Tze204_Davzgqq0 TS0601 custom device implementation.""" signature = { MODELS_INFO: [("_TZE204_davzgqq0", "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, TuyaOnOffManufCluster.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, MoesSwitchManufCluster, TuyaOnOffNM, ], OUTPUT_CLUSTERS: [ Time.cluster_id, Ota.cluster_id, ] } } } ```
MattWestb commented 1 year ago

Try using the EnchantedDevice device class: https://github.com/zigpy/zha-device-handlers/blob/22968954cacaff4fc2b00e02ca1bd5622f637393/zhaquirks/tuya/mcu/__init__.py#L694

pkejval commented 1 year ago

I hope I used that EnchantedDevice class well - code below. But it's still not working. Every time I change quirk code I remove device from ZHA, restart whole HA and then repair device.

Quirk code ```python """Quirk for _TZE204_davzgqq0 TS0601.""" import asyncio from zigpy.profiles import zha from zigpy.quirks import CustomDevice from zigpy.zcl.clusters.general import ( Basic, Groups, Ota, Scenes, Time, ) from zhaquirks.const import ( DEVICE_TYPE, ENDPOINTS, INPUT_CLUSTERS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, ) from zhaquirks.tuya import TuyaSwitch from zhaquirks.tuya.mcu import ( MoesSwitchManufCluster, TuyaOnOffNM, TuyaOnOffManufCluster, ) class EnchantedDevice(CustomDevice): """Class for enchanted Tuya devices which needs to be unlocked by casting a 'spell'.""" def __init__(self, *args, **kwargs): """Initialize with task.""" super().__init__(*args, **kwargs) self._init_device_task = asyncio.create_task(self.spell()) async def spell(self) -> None: """Initialize device so that all endpoints become available.""" attr_to_read = [4, 0, 1, 5, 7, 0xFFFE] basic_cluster = self.endpoints[1].in_clusters[0] await basic_cluster.read_attributes(attr_to_read) class Tze204Davzgqq0Ts0601(EnchantedDevice): """_Tze204_Davzgqq0 TS0601 custom device implementation.""" signature = { MODELS_INFO: [("_TZE204_davzgqq0", "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, TuyaOnOffManufCluster.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, MoesSwitchManufCluster, TuyaOnOffNM, ], OUTPUT_CLUSTERS: [ Time.cluster_id, Ota.cluster_id, ] } } } ```
MattWestb commented 1 year ago

You can importing the EnchantedDevice class and you dont need putting the code in your quirk. Meny new devices need it for working OK and also changing device cluster and endpoints functions.

If you have good debug logging set you can see if the system is sending the attribute reads to the device (is only working then device is new joining).

I dont have the knowledge of coding then im not one code warier ;-((

pkejval commented 1 year ago

I have logging set up like this. Will be helpful for anyone when I upload home-assistant.log here?

configuration.yaml ```yaml logger: default: info logs: homeassistant.core: debug homeassistant.components.zha: debug bellows.zigbee.application: debug bellows.ezsp: debug zigpy: debug zigpy_deconz.zigbee.application: debug zigpy_deconz.api: debug zigpy_xbee.zigbee.application: debug zigpy_xbee.api: debug zigpy_zigate: debug zigpy_znp: debug zhaquirks: debug ```

EDIT: Added log where I removed device from HA and repair it again.

home-assistant.log

pkejval commented 1 year ago

Zigbee2MQTT has similar request: https://github.com/Koenkk/zigbee2mqtt/issues/16118

They found out datapoints but I don't know how to translate them into ZHA quirk code. Can anyone help me with that?

tuyaDatapoints: [
          [1, 'energy', tuya.valueConverter.divideBy100],
          [6, null, tuya.valueConverter.phaseVariant2],
          [16, 'state', tuya.valueConverter.onOff],
          // {"type":"Buffer","data":[4,1,0,30]} - leakage enabled
          // {"type":"Buffer","data":[4,0,0,30]} - leakage disabled
          [17, 'leakage_threshold', valueConverter.thresholdVariant1({4: 4}, {'ON': 1, 'OFF': 0})],
          //[17, 'leakage_threshold', valueConverter.threshold2],
          // {"type":"Buffer","data":[1,1,0,30,3,0,0,250,4,0,0,150]} - overcurr enabled 30A | overvolt enabled 250V | undervolt enabled 150V
          // {"type":"Buffer","data":[1,0,0,21,3,0,0,250,4,0,0,150]} - overcurr disabled 21A | overvolt disabled 250V | undervolt disabled 150V
          [18, 'thresholds', valueConverter.thresholdVariant2(['overcurrent', 'overvoltage', 'undervoltage'], {1: 1, 3: 3, 4: 4}, {'ON': 1, 'OFF': 0})],
          //[18, 'overcurrent_threshold', valueConverter.thresholdVariant2([], {1: 1}, {'ON': 1, 'OFF': 0})],
          //[18, 'overvoltage_threshold', valueConverter.thresholdVariant2([], {3: 3}, {'ON': 1, 'OFF': 0})],
          //[18, 'undervoltage_threshold', valueConverter.thresholdVariant2([], {4: 4}, {'ON': 1, 'OFF': 0})],
          [19, 'breaker_id', tuya.valueConverter.raw],
      ],
TolianIPB commented 1 year ago

Add support pleaze.

alex-kms commented 1 year ago

Are there those who do not use the device in ZHA, but can integrate it into ZHA ?

XpycTee commented 1 year ago

Hi, I made a working custom quirk using attributes from Hiking Power Meter

data about voltage, current and power is transmitted in the attribute 0x0006.

---v---------mA---------W--- [08 98] 00 [1A A2] 00 [05 DC]

v/10=RMS voltage mA = RMS current W = Active power

The status of the switch in the attribute 0x0110 Total Energy Delivered kWh in attr 0x0201

ts0601_din_power.py.txt

lukyrys commented 10 months ago

Is there any way to set last state after power outage?

pkejval commented 10 months ago

@XpycTee Thank you very much! Your quirk is working great.

@lukyrys I'm thinking about it right now. Maybe some HA automation?

lukyrys commented 10 months ago

@pkejval I don't think it would be good because if these devices are connected to the start of power line and there is a power outage, what will turn it on. I think the version with wifi and that application can turn it on in the device settings. i dont know if this is available if is zigbee controlled by tuya maybe yes

MattWestb commented 10 months ago

Zigbee standard is having attribute that can being set for it but in "normal tuya zigbee devices" is not zigbee standard but we is adding it to then on the cluster. tuya MCU devices is completely different then its not using Zigbee standard at all only piping commands thru Ziogbee so all must being implanted with DPs for it shall working OK.

agnus777 commented 5 months ago

Hi All,

Has anyone managed to enable the OnOff function to be able to set the status upon reboot?

agnus777 commented 5 months ago

At the end, I purchase a tuya zigbee hub and I noticed that neither in the official app is possible to manage the onoff status at start-up and this look so pity and strange. Any way from your more experience on zigbee appliances and zha to manage it?

Screenshot_2024-01-09-19-53-09-62_d448303990bb92d03c4d800220c6084a

pkejval commented 5 months ago

@agnus777 I managed to make ON/OFF work with this quirk in /config/zha_quirks/ts0601_din_power.py Then turn it ON in Automations if it is OFF.

"""Tuya Din Power Meter."""
from zigpy.profiles import zha
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

"""Earu Power Meter Attributes"""
EARU_TOTAL_ENERGY_DELIVERED_ATTR = 0x0201
EARU_VOLTAGE_CURRENT_POWER_ATTR = 0x0006
EARU_DIN_SWITCH_ATTR = 0x0110

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 EaruManufClusterDinPower(TuyaManufClusterAttributes):
    """Manufacturer Specific Cluster of the Tuya Power Meter device."""

    attributes = {
        EARU_TOTAL_ENERGY_DELIVERED_ATTR: ("energy_delivered", t.uint16_t, True),
        EARU_VOLTAGE_CURRENT_POWER_ATTR: ("voltage_power", t.uint64_t, True),
        EARU_DIN_SWITCH_ATTR: ("switch", t.uint8_t, True),

    }

    def _update_attribute(self, attrid, value):
        super()._update_attribute(attrid, value)
        if attrid == EARU_DIN_SWITCH_ATTR:
            self.endpoint.device.switch_bus.listener_event(SWITCH_EVENT, 16, value)
        elif attrid == EARU_TOTAL_ENERGY_DELIVERED_ATTR:
            self.endpoint.smartenergy_metering.energy_deliver_reported(value / 100)
        elif attrid == EARU_VOLTAGE_CURRENT_POWER_ATTR:
            self.endpoint.electrical_measurement.voltage_reported((value>>48 & 0xffff)/10)
            self.endpoint.electrical_measurement.current_reported((value>>24 & 0xffff))
            self.endpoint.electrical_measurement.power_reported((value & 0xffff))

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.uint16_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 TuyaPowerMeter(TuyaSwitch):
    """Tuya power meter device."""

    def __init__(self, *args, **kwargs):
        """Init device."""
        self.switch_bus = Bus()
        super().__init__(*args, **kwargs)

    signature = {
        # "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
        #                       maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
        #                       maximum_outgoing_transfer_size=82 descriptor_capability_field=0>",
        # device_version=1
        # input_clusters=[0x0000, 0x0004, 0x0005, 0xef00]
        # output_clusters=[0x000a, 0x0019]
        MODELS_INFO: [
            ("_TZE200_byzdayie", "TS0601"),
            ("_TZE200_ewxhg6o9", "TS0601"),
        ],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=51
            # device_version=1
            # input_clusters=[0, 4, 5, 61184]
            # output_clusters=[10, 25]>
            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": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
        #                       maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
        #                       maximum_outgoing_transfer_size=82 descriptor_capability_field=0>",
        # device_version=1
        # input_clusters=[0x0000, 0x0004, 0x0005, 0xef00]
        # output_clusters=[0x000a, 0x0019]
        MODELS_INFO: [
            ("_TZE200_bkkmqmyo", "TS0601")
        ],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=51
            # device_version=1
            # input_clusters=[0, 4, 5, 61184]
            # output_clusters=[10, 25]>
            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 EaruPowerMeter(TuyaSwitch):
    """Earu Power Meter Device"""

    signature = {
        MODELS_INFO: [
            ("_TZE204_davzgqq0", "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,
                    EaruManufClusterDinPower,
                    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: [],
            },
        }
    }
lukyrys commented 5 months ago

@pkejval does it solve auto power on after power failure ? i mean same config (last state) which is on my water valve obrazek

pkejval commented 5 months ago

No it doesn't because the device can't do it as you noticed in the official tuya app. Only way out is Tuya or Home Assistant automation. I have automation running every minute to check state:

st 10. 1. 2024 v 3:12 odesílatel LuRy @.***> napsal:

@pkejval https://github.com/pkejval does it solve auto power on after power failure ? i mean same config which is on my water valve obrazek.png (view on web) https://github.com/zigpy/zha-device-handlers/assets/2318346/2cd4dc6c-afdb-45b7-8018-dd349df938d3

— Reply to this email directly, view it on GitHub https://github.com/zigpy/zha-device-handlers/issues/2061#issuecomment-1884079832, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALOKP45DAMR2OHP3O7CMVY3YNX2JVAVCNFSM6AAAAAATP5NNNGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQOBUGA3TSOBTGI . You are receiving this because you were mentioned.Message ID: @.***>

agnus777 commented 5 months ago

@pkejval I will try this automation! thanks a lot for the nice work done!.

perhaps this function is not integrated due to some regulations on the operation of RCD breakers... I don't know. in any case I wrote to the seller on aliexpress, which is the official Earu store, if there is a way to set this or not

I will post update if any

No it doesn't because the device can't do it as you noticed in the official tuya app. Only way out is Tuya or Home Assistant automation. I have automation running every minute to check state: - id: "1692860675855" alias: Automatizace - keep breakers on description: "" trigger: - platform: template value_template: '{{ (not is_state("switch.jistic_1", "on")) or (not is_state( "switch.jistic_2", "on")) or (not is_state("switch.jistic_3", "on")) }}' alias: Not all are ON - platform: time_pattern minutes: /1 condition: - condition: template value_template: '{{ (not is_state("switch.jistic_1", "on")) or (not is_state( "switch.jistic_2", "on")) or (not is_state("switch.jistic_3", "on")) }}' action: - service: switch.turn_on data: {} target: entity_id: - switch.jistic_1 - switch.jistic_2 - switch.jistic_3 mode: single st 10. 1. 2024 v 3:12 odesílatel LuRy @.> napsal: @pkejval https://github.com/pkejval does it solve auto power on after power failure ? i mean same config which is on my water valve obrazek.png (view on web) https://github.com/zigpy/zha-device-handlers/assets/2318346/2cd4dc6c-afdb-45b7-8018-dd349df938d3 — Reply to this email directly, view it on GitHub <#2061 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALOKP45DAMR2OHP3O7CMVY3YNX2JVAVCNFSM6AAAAAATP5NNNGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQOBUGA3TSOBTGI . You are receiving this because you were mentioned.Message ID: @.>