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
747 stars 684 forks source link

[Device Support Request] vendor: 'TuYa' modelID: 'TS0601', manufacturerName: '_TZE200_whkgqxse' model: 'JM-TRH-ZGB-V1' #1771

Closed abstiger closed 1 year ago

abstiger commented 2 years ago

Is your feature request related to a problem? Please describe.

I was able to connect it to the HA zigbee, but neither of the sensors are working in HA.

Device Info: https://www.aliexpress.com/item/3256803794332794.html

Describe the solution you'd like

can support this device with ZHA

Device signature ```yaml Paste the device signature here. Don't remove the extra line breaks outside the ``` marks. ```
Diagnostic information ```yaml { "home_assistant": { "installation_type": "Home Assistant Container", "version": "2022.9.2", "dev": false, "hassio": false, "virtualenv": false, "python_version": "3.10.5", "docker": true, "arch": "x86_64", "timezone": "Asia/Shanghai", "os_name": "Linux", "os_version": "5.4.0-125-generic", "run_as_root": true }, "custom_components": {}, "integration_manifest": { "domain": "zha", "name": "Zigbee Home Automation", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ "bellows==0.33.1", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.79", "zigpy-deconz==0.18.1", "zigpy==0.50.3", "zigpy-xbee==0.15.0", "zigpy-zigate==0.9.2", "zigpy-znp==0.8.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*" } ], "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": 41779, "manufacturer": "_TZE200_whkgqxse", "model": "TS0601", "name": "_TZE200_whkgqxse TS0601", "quirk_applied": true, "quirk_class": "tuya.TuyaTempHumiditySensor", "manufacturer_code": 4417, "power_source": "Battery or Unknown", "lqi": null, "rssi": null, "last_seen": "2022-09-20T10:58:33", "available": true, "device_type": "EndDevice", "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=4417, maximum_buffer_size=66, maximum_incoming_transfer_size=66, server_mask=10752, maximum_outgoing_transfer_size=66, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=True, *is_full_function_device=False, *is_mains_powered=False, *is_receiver_on_when_idle=False, *is_router=False, *is_security_capable=False)", "endpoints": { "1": { "profile_id": 260, "device_type": "0x0302", "in_clusters": [ "0x0001", "0x0402", "0x0405", "0xef00" ], "out_clusters": [ "0x000a", "0x0019" ] } } }, "active_coordinator": false, "entities": [ { "entity_id": "sensor.tze200_whkgqxse_ts0601_battery", "name": "_TZE200_whkgqxse TS0601" }, { "entity_id": "sensor.tze200_whkgqxse_ts0601_temperature", "name": "_TZE200_whkgqxse TS0601" }, { "entity_id": "sensor.tze200_whkgqxse_ts0601_humidity", "name": "_TZE200_whkgqxse TS0601" } ], "neighbors": [], "endpoint_names": [ { "name": "TEMPERATURE_SENSOR" } ], "user_given_name": null, "device_reg_id": "4c675c047ff7ec11fd3038c2cb14a6cb", "area_id": "er_tong_fang", "cluster_details": { "1": { "device_type": { "name": "TEMPERATURE_SENSOR", "id": 770 }, "profile_id": 260, "in_clusters": { "0xef00": { "endpoint_attribute": "tuya_manufacturer", "attributes": {}, "unsupported_attributes": {} }, "0x0402": { "endpoint_attribute": "temperature", "attributes": {}, "unsupported_attributes": {} }, "0x0405": { "endpoint_attribute": "humidity", "attributes": {}, "unsupported_attributes": {} }, "0x0001": { "endpoint_attribute": "power", "attributes": {}, "unsupported_attributes": {} } }, "out_clusters": { "0x0019": { "endpoint_attribute": "ota", "attributes": {}, "unsupported_attributes": {} }, "0x000a": { "endpoint_attribute": "time", "attributes": {}, "unsupported_attributes": {} } } } } } } ```
Additional logs ``` Paste any additional debug logs here. Don't remove the extra line breaks outside the ``` marks. ```

Additional context

Zigbee2MQTT has already supported, see this commit: https://github.com/Koenkk/zigbee-herdsman-converters/commit/8b5b6d50bb436c3664c87b12a44c8075dbfb7586

abstiger commented 2 years ago

the diagnostic information shows that this device signature is same with current tuya model("_TZE200_bjawzodf", "TS0601") which supported by quirk file: ts0601_sensor.py

so i just added the new model to the MODELS_INFO array.

i've tested that with

docker container ls 
docker -exec -it af578040bdff /bin/bash
vi /usr/local/lib/python3.10/site-packages/zhaquirks/tuya/ts0601_sensor.py

then restart home-assistant , currently the device can be recognized with quirk: tuya_ts0601_sensor.TuyaTempHumiditySensor

and the Temperature shows correctly, but the Humidiy and Battery keep Unknown.

javicalle commented 2 years ago

Maybe you can try with the EnchantedDevice class:

  1. Import the class
    from zhaquirks.tuya.mcu import EnchantedDevice
  2. Add it in the class definition:
class TuyaTempHumiditySensor(EnchantedDevice, CustomDevice):
    """Custom device representing tuya temp and humidity sensor with e-ink screen."""

    .../...

Not the final implementation, just to check if it works.

If not working, the debug logs from the device will give us some info about what can be happening here.

abstiger commented 2 years ago

Thanks, here are some steps i did:

  1. restore the ts0601_sensor.py file in container pip packages
  2. copy the ts0601_sensor.py to custom_zha_quirks path https://github.com/zigpy/zha-device-handlers/discussions/693#discussioncomment-857274
  3. did some modification as you suggested, here is my custom quirk:
    
    """Tuya temp and humidity sensor with e-ink screen."""

from typing import Dict

from zigpy.profiles import zha from zigpy.quirks import CustomDevice from zigpy.zcl.clusters.general import Basic, Groups, Ota, Scenes, Time from zigpy.zcl.clusters.measurement import RelativeHumidity, TemperatureMeasurement

from zhaquirks.const import ( DEVICE_TYPE, ENDPOINTS, INPUT_CLUSTERS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, SKIP_CONFIGURATION, ) from zhaquirks.tuya import TuyaLocalCluster, TuyaPowerConfigurationCluster2AAA from zhaquirks.tuya.mcu import DPToAttributeMapping, EnchantedDevice, TuyaDPType, TuyaMCUCluster

NOTES:

The data comes in as a string on cluster, if there is nothing set up you may see these lines in the logs:

Unknown message (b'19830100a40102000400000118') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'

28.0 degrees

Unknown message (b'19840100a5020200040000022c') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'

55.6% humid

Unknown message (b'19850100a60402000400000064') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'

100% battery

class TuyaTemperatureMeasurement(TemperatureMeasurement, TuyaLocalCluster): """Tuya local TemperatureMeasurement cluster."""

class TuyaRelativeHumidity(RelativeHumidity, TuyaLocalCluster): """Tuya local RelativeHumidity cluster."""

class TemperatureHumidityManufCluster(TuyaMCUCluster): """Tuya Manufacturer Cluster with Temperature and Humidity data points."""

dp_to_attribute: Dict[int, DPToAttributeMapping] = {
    1: DPToAttributeMapping(
        TuyaTemperatureMeasurement.ep_attribute,
        "measured_value",
        dp_type=TuyaDPType.VALUE,
        converter=lambda x: x * 10,  # decidegree to centidegree
    ),
    2: DPToAttributeMapping(
        TuyaRelativeHumidity.ep_attribute,
        "measured_value",
        dp_type=TuyaDPType.VALUE,
        converter=lambda x: x * 10,  # decipercent to centipercent
    ),
    4: DPToAttributeMapping(
        TuyaPowerConfigurationCluster2AAA.ep_attribute,
        "battery_percentage_remaining",
        dp_type=TuyaDPType.VALUE,
        converter=lambda x: x * 2,  # double reported percentage
    ),
}

data_point_handlers = {
    1: "_dp_2_attr_update",
    2: "_dp_2_attr_update",
    4: "_dp_2_attr_update",
}

class TuyaTempHumiditySensor(EnchantedDevice, CustomDevice): """Custom device representing tuya temp and humidity sensor with e-ink screen."""

signature = {
    # <SimpleDescriptor endpoint=1, profile=260, device_type=81
    # device_version=1
    # input_clusters=[4, 5, 61184, 0]
    # output_clusters=[25, 10]>
    MODELS_INFO: [("_TZE200_whkgqxse", "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,
                TemperatureHumidityManufCluster.cluster_id,
            ],
            OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
        }
    },
}

replacement = {
    SKIP_CONFIGURATION: True,
    ENDPOINTS: {
        1: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR,
            INPUT_CLUSTERS: [
                TemperatureHumidityManufCluster,  # Single bus for temp, humidity, and battery
                TuyaTemperatureMeasurement,
                TuyaRelativeHumidity,
                TuyaPowerConfigurationCluster2AAA,
            ],
            OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
        }
    },
}

4. enabled zha debug logs https://www.home-assistant.io/integrations/zha/#debug-logging  
then `grep _TZE200_whkgqxse home-assistant.log`  got below: 
```log
2022-09-21 08:18:11.190 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=sensor.tze200_whkgqxse_ts0601_battery, old_state=None, new_state=<state sensor.tze200_whkgqxse_ts0601_battery=unknown; state_class=measurement, unit_of_measurement=%, device_class=battery, friendly_name=_TZE200_whkgqxse TS0601 Battery @ 2022-09-21T08:18:11.190003+08:00>>
2022-09-21 08:18:11.190 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=sensor.tze200_whkgqxse_ts0601_temperature, old_state=None, new_state=<state sensor.tze200_whkgqxse_ts0601_temperature=23.4; state_class=measurement, unit_of_measurement=°C, device_class=temperature, friendly_name=_TZE200_whkgqxse TS0601 Temperature @ 2022-09-21T08:18:11.190610+08:00>>
2022-09-21 08:18:11.191 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=sensor.tze200_whkgqxse_ts0601_humidity, old_state=None, new_state=<state sensor.tze200_whkgqxse_ts0601_humidity=unknown; state_class=measurement, unit_of_measurement=%, device_class=humidity, friendly_name=_TZE200_whkgqxse TS0601 Humidity @ 2022-09-21T08:18:11.191385+08:00>>
abstiger commented 2 years ago

i've found there are some warnings in the log:

grep WARNING home-assistant.log :

2022-09-21 08:56:41.528 WARNING (MainThread) [zigpy.zcl] [0xF537:1:0xef00] Data remains after deserializing ZCL frame: b'\x02\x02\x00\x04\x00\x00\x00?\x04\x02\x00\x04\x00\x00\x00d\n\x02\x00\x04\x00\x00\x01h\x0b\x02\x00\x04\x00\x00\x00\xc8'
2022-09-21 08:56:42.292 WARNING (MainThread) [zigpy.zcl] [0xF537:1:0xef00] Data remains after deserializing ZCL frame: b'\r\x02\x00\x04\x00\x00\x00\x14\x11\x02\x00\x04\x00\x00\x00\x1e\t\x04\x00\x01\x00'
2022-09-21 08:56:42.294 WARNING (MainThread) [zigpy.zcl] [0xF537:1:0xef00] No 'handle_set_data_response' tuya handler found for set_data_response(data=TuyaCommand(status=9, tsn=163, dp=12, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'F\x00\x00\x00', *payload=70)))

grep zigpy.zcl home-assistant.log :

2022-09-21 08:56:43.034 DEBUG (MainThread) [zigpy.zcl] [0xF537:1:0xef00] Received ZCL frame: b'\x09\x5E\x02\x09\xA7\x0C\x02\x00\x04\x00\x00\x00\x46\x0D\x02\x00\x04\x00\x00\x00\x14\x11\x02\x00\x04\x00\x00\x00\x1E\x09\x04\x00\x01\x00'
2022-09-21 08:56:43.034 DEBUG (MainThread) [zigpy.zcl] [0xF537:1:0xef00] Decoded ZCL frame header: ZCLHeader(frame_control=FrameControl(frame_type=<FrameType.CLUSTER_COMMAND: 1>, is_manufacturer_specific=0, direction=<Direction.Client_to_Server: 1>, disable_default_response=0, reserved=0, *is_cluster=True, *is_general=False, *is_reply=True), tsn=94, command_id=2, *direction=<Direction.Client_to_Server: 1>, *is_reply=True)
2022-09-21 08:56:43.035 DEBUG (MainThread) [zigpy.zcl] [0xF537:1:0xef00] Decoded ZCL frame: TemperatureHumidityManufCluster:set_data_response(data=TuyaCommand(status=9, tsn=167, dp=12, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'F\x00\x00\x00', *payload=70)))
2022-09-21 08:56:43.035 WARNING (MainThread) [zigpy.zcl] [0xF537:1:0xef00] Data remains after deserializing ZCL frame: b'\r\x02\x00\x04\x00\x00\x00\x14\x11\x02\x00\x04\x00\x00\x00\x1e\t\x04\x00\x01\x00'
2022-09-21 08:56:43.036 DEBUG (MainThread) [zigpy.zcl] [0xF537:1:0xef00] Received command 0x02 (TSN 94): set_data_response(data=TuyaCommand(status=9, tsn=167, dp=12, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'F\x00\x00\x00', *payload=70)))
2022-09-21 08:56:43.037 DEBUG (MainThread) [zigpy.zcl] [0xF537:1:0xef00] No datapoint handler for TuyaCommand(status=9, tsn=167, dp=12, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'F\x00\x00\x00', *payload=70))
2022-09-21 08:56:43.037 WARNING (MainThread) [zigpy.zcl] [0xF537:1:0xef00] No 'handle_set_data_response' tuya handler found for set_data_response(data=TuyaCommand(status=9, tsn=167, dp=12, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'F\x00\x00\x00', *payload=70)))
2022-09-21 08:56:43.038 DEBUG (MainThread) [zigpy.zcl] [0xF537:1:0xef00] Sending reply header: ZCLHeader(frame_control=FrameControl(frame_type=<FrameType.GLOBAL_COMMAND: 0>, is_manufacturer_specific=False, direction=<Direction.Client_to_Server: 1>, disable_default_response=1, reserved=0, *is_cluster=False, *is_general=True, *is_reply=True), tsn=94, command_id=<GeneralCommand.Default_Response: 11>, *direction=<Direction.Client_to_Server: 1>, *is_reply=True)
2022-09-21 08:56:43.039 DEBUG (MainThread) [zigpy.zcl] [0xF537:1:0xef00] Sending reply: Default_Response(command_id=2, status=<Status.UNSUP_CLUSTER_COMMAND: 129>)
javicalle commented 2 years ago

A few comments.

Have you tried to remove the device from HA and pires it again? We want to fire the bind process here.

According to Z2M dp=12 is the nousMaxHumi. We can address the issue later.

Maybe it will be the same result but try with grep 0xF537 home-assistant.log to get the device logs. Also attach the pairing process logs.

abstiger commented 2 years ago

removed and paired the device, Nothing has changed, the problem remains

here is pairing process logs from HA web Add Device then SHOW LOGS:

pairing.log

javicalle commented 2 years ago

Related to: #1566

That will need a major change in Tuya handler. I will try to check in the weekend

javicalle commented 2 years ago

A couple of PRs that can be helpfull here had been merged:

With the new HA version it is expected that:

  1. the device successfully sync date/time
  2. multi-attribute reports can be managed

In your logs DP12 and DP15 is already reported, with the next version it can be also this DPs:

    nousTemperature: 1,
    nousHumidity: 2,
    nousBattery: 4,
    nousTempUnitConvert: 9,
    nousMaxTemp: 10,
    nousMinTemp: 11,
    nousMaxHumi: 12,
    nousMinHumi: 13,
    nousTempAlarm: 14,
    nousHumiAlarm: 15,
    nousTempSensitivity: 19,
    nousReportInterval: 17,

Most of these DPs are already in the TuyaTemperatureHumidityAlarmCluster class:

My assumption is that the nousMaxXXX and nousMinXXX attributes define the limits of the alarm and the nousXXXAlarm attributes should be the Enum with the type of alarm:

The nousTempUnitConvert should be an Enum to change the display (and measures?) units (ºC/ºF). The nousReportInterval should be a value between 1min to 120min. Not sure about the nousTempSensitivity.

Sooooo, what I would do is:

abstiger commented 2 years ago

thanks, i'll give it a try after new HA version released.

abstiger commented 2 years ago

@javicalle after upgraded to the Home Assistant 2022.10.1 Version. the Humidity and Battery can be shown, except that Humidity is shown like 7% but actually it's 70%

i tried to modify my custom quark file tuya_ts0601_tze200_whkgqxse.py as you suggested. but i'm not sure about if that is correct and how to add the missing attributes(nousTempUnitConvert: 9, nousTempSensitivity: 19, nousReportInterval: 17,) could you please help checking the quark file?

then i restart HA without re-pairing, i'll re-pairing it when i get home.

"""Tuya temp and humidity sensor with e-ink screen."""

from typing import Dict

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.measurement import RelativeHumidity, TemperatureMeasurement

from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
    SKIP_CONFIGURATION,
)
from zhaquirks.tuya import TuyaLocalCluster, TuyaPowerConfigurationCluster2AAA
from zhaquirks.tuya.ts0201 import TuyaTemperatureHumidityAlarmCluster
from zhaquirks.tuya.mcu import DPToAttributeMapping, EnchantedDevice, TuyaDPType, TuyaMCUCluster

# NOTES:
# The data comes in as a string on cluster, if there is nothing set up you may see these lines in the logs:
# Unknown message (b'19830100a40102000400000118') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'
#                                          28.0 degrees
# Unknown message (b'19840100a5020200040000022c') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'
#                                          55.6% humid
# Unknown message (b'19850100a60402000400000064') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'
#                                          100% battery

class TuyaTemperatureMeasurement(TemperatureMeasurement, TuyaLocalCluster):
    """Tuya local TemperatureMeasurement cluster."""

class TuyaRelativeHumidity(RelativeHumidity, TuyaLocalCluster):
    """Tuya local RelativeHumidity cluster."""

class TemperatureHumidityManufCluster(TuyaMCUCluster):
    """Tuya Manufacturer Cluster with Temperature and Humidity data points."""

    attributes = {
        0xD009: ("temperature_unit_converter", t.CharacterString, True),
        # Alarm information
        0xD013: ("temperature_sensitivity", t.CharacterString, True),
        0xD011: ("report_interval", t.uint16_t, True),
        # Unknown
        0xD010: ("unknown", t.uint8_t, True),
    }

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        1: DPToAttributeMapping(
            TuyaTemperatureMeasurement.ep_attribute,
            "measured_value",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 10,  # decidegree to centidegree
        ),
        2: DPToAttributeMapping(
            TuyaRelativeHumidity.ep_attribute,
            "measured_value",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 100,  
        ),
        4: DPToAttributeMapping(
            TuyaPowerConfigurationCluster2AAA.ep_attribute,
            "battery_percentage_remaining",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 2,  # double reported percentage
        ),
        10: DPToAttributeMapping(
            TuyaTemperatureHumidityAlarmCluster.ep_attribute,
            "alarm_temperature_max",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 10,  # decipercent to centipercent
        ),
        11: DPToAttributeMapping(
            TuyaTemperatureHumidityAlarmCluster.ep_attribute,
            "alarm_temperature_min",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 10,  # decipercent to centipercent
        ),
        12: DPToAttributeMapping(
            TuyaTemperatureHumidityAlarmCluster.ep_attribute,
            "alarm_humidity_max",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 10,  # decipercent to centipercent
        ),
        13: DPToAttributeMapping(
            TuyaTemperatureHumidityAlarmCluster.ep_attribute,
            "alarm_humidity_min",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 10,  # decipercent to centipercent
        ),
        14: DPToAttributeMapping(
            TuyaTemperatureHumidityAlarmCluster.ep_attribute,
            "alarm_temperature",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x,
        ),
        15: DPToAttributeMapping(
            TuyaTemperatureHumidityAlarmCluster.ep_attribute,
            "alarm_humidity",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x,
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        4: "_dp_2_attr_update",
        10: "_dp_2_attr_update",
        11: "_dp_2_attr_update",
        12: "_dp_2_attr_update",
        13: "_dp_2_attr_update",
        14: "_dp_2_attr_update",
        15: "_dp_2_attr_update",
    }

class TuyaTempHumiditySensor(EnchantedDevice, CustomDevice):
    """Custom device representing tuya temp and humidity sensor with e-ink screen."""

    signature = {
        # <SimpleDescriptor endpoint=1, profile=260, device_type=81
        # device_version=1
        # input_clusters=[4, 5, 61184, 0]
        # output_clusters=[25, 10]>
        MODELS_INFO: [("_TZE200_whkgqxse", "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,
                    TemperatureHumidityManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
            }
        },
    }

    replacement = {
        SKIP_CONFIGURATION: True,
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR,
                INPUT_CLUSTERS: [
                    TemperatureHumidityManufCluster,  # Single bus for temp, humidity, and battery
                    TuyaTemperatureHumidityAlarmCluster,
                    TuyaTemperatureMeasurement,
                    TuyaRelativeHumidity,
                    TuyaPowerConfigurationCluster2AAA,
                ],
                OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
            }
        },
    }

here are some log info:

2022-10-09 09:59:08.197 DEBUG (MainThread) [homeassistant.components.zha.core.device] [0x35D9](TS0601): Device seen - marking the device available and resetting counter
2022-10-09 09:59:08.197 DEBUG (MainThread) [homeassistant.components.zha.core.device] [0x35D9](TS0601): Update device availability -  device available: True - new availability: True - changed: False
2022-10-09 09:59:09.213 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=61184, SrcAddr=0x35D9, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=51, SecurityUse=<Bool.false: 0>, TimeStamp=14090783, TSN=0, Data=b'\x09\x5B\x02\x36\xA3\x01\x02\x00\x04\x00\x00\x00\xBE\x02\x02\x00\x04\x00\x00\x00\x44\x04\x02\x00\x04\x00\x00\x00\x64\x0A\x02\x00\x04\x00\x00\x01\x68\x0B\x02\x00\x04\x00\x00\x00\xC8', MacSrcAddr=0x35D9, MsgResultRadius=29)
2022-10-09 09:59:09.214 DEBUG (MainThread) [zigpy.application] Received a packet: ZigbeePacket(src=AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0x35D9), src_ep=1, dst=AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0x0000), dst_ep=1, source_route=None, extended_timeout=False, tsn=0, profile_id=260, cluster_id=61184, data=Serialized[b'\t[\x026\xa3\x01\x02\x00\x04\x00\x00\x00\xbe\x02\x02\x00\x04\x00\x00\x00D\x04\x02\x00\x04\x00\x00\x00d\n\x02\x00\x04\x00\x00\x01h\x0b\x02\x00\x04\x00\x00\x00\xc8'], tx_options=<TransmitOptions.NONE: 0>, radius=29, non_member_radius=0, lqi=51, rssi=None)
2022-10-09 09:59:09.214 DEBUG (MainThread) [zigpy.zcl] [0x35D9:1:0xef00] Received ZCL frame: b'\t[\x026\xa3\x01\x02\x00\x04\x00\x00\x00\xbe\x02\x02\x00\x04\x00\x00\x00D\x04\x02\x00\x04\x00\x00\x00d\n\x02\x00\x04\x00\x00\x01h\x0b\x02\x00\x04\x00\x00\x00\xc8'
2022-10-09 09:59:09.214 DEBUG (MainThread) [zigpy.zcl] [0x35D9:1:0xef00] Decoded ZCL frame header: ZCLHeader(frame_control=FrameControl(frame_type=<FrameType.CLUSTER_COMMAND: 1>, is_manufacturer_specific=0, direction=<Direction.Client_to_Server: 1>, disable_default_response=0, reserved=0, *is_cluster=True, *is_general=False, *is_reply=True), tsn=91, command_id=2, *direction=<Direction.Client_to_Server: 1>, *is_reply=True)
2022-10-09 09:59:09.215 DEBUG (MainThread) [zigpy.zcl] [0x35D9:1:0xef00] Decoded ZCL frame: TemperatureHumidityManufCluster:set_data_response(data=TuyaCommand(status=54, tsn=163, datapoints=[TuyaDatapointData(dp=1, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'\xbe\x00\x00\x00', *payload=190)), TuyaDatapointData(dp=2, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'D\x00\x00\x00', *payload=68)), TuyaDatapointData(dp=4, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'd\x00\x00\x00', *payload=100)), TuyaDatapointData(dp=10, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'h\x01\x00\x00', *payload=360)), TuyaDatapointData(dp=11, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'\xc8\x00\x00\x00', *payload=200))]))
2022-10-09 09:59:09.216 DEBUG (MainThread) [zigpy.zcl] [0x35D9:1:0xef00] Received command 0x02 (TSN 91): set_data_response(data=TuyaCommand(status=54, tsn=163, datapoints=[TuyaDatapointData(dp=1, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'\xbe\x00\x00\x00', *payload=190)), TuyaDatapointData(dp=2, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'D\x00\x00\x00', *payload=68)), TuyaDatapointData(dp=4, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'd\x00\x00\x00', *payload=100)), TuyaDatapointData(dp=10, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'h\x01\x00\x00', *payload=360)), TuyaDatapointData(dp=11, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'\xc8\x00\x00\x00', *payload=200))]))
abstiger commented 2 years ago

here is my pairing log pairing.log

javicalle commented 2 years ago

except that Humidity is shown like 7% but actually it's 70%

That can be fixed here:

        2: DPToAttributeMapping(
            TuyaRelativeHumidity.ep_attribute,
            "measured_value",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 1000,  
        ),

Probably would be the same in DP 12 and 13.

From the log I can see a couple of things:

  1. that #1779 handle all the attributes report from this device 🎉
  2. the device is reporting that values (and its current assignation)
DP value (raw) value (converted) attribute
1 190 19 (ºC?) temperature
2 68 68 (%) humidity
4 100 100 (%) battery percentage
10 360 36 (ºC?) alarm_temperature_max
11 200 20 (ºC?) alarm_temperature_min

Can you check if the alarm values are fine to you? Is possible to read these values from device to verify if they are the same?

javicalle commented 2 years ago

how to add the missing attributes(nousTempUnitConvert: 9, nousTempSensitivity: 19, nousReportInterval: 17,)

I'll give one to you, but the rest would be the same.

Let's take nousTempUnitConvert as example. The attribute is already defined as:

    attributes = {
        0xD009: ("temperature_unit_converter", t.CharacterString, True),
        # Alarm information
        0xD013: ("temperature_sensitivity", t.CharacterString, True),
        0xD011: ("report_interval", t.uint16_t, True),
        # Unknown
        0xD010: ("unknown", t.uint8_t, True),
    }

The next step is to map the attribute and the DP. That is done in the dp_to_attribute and data_point_handlers. Just add the definition as:

        9: DPToAttributeMapping( # <-- the new DP
            TemperatureHumidityManufCluster.ep_attribute, # <-- the class that have the attribute
            "temperature_unit_converter", # <-- the attribute name
            dp_type=TuyaDPType.ENUM, # <-- the DP type 
        ),

.../...

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        4: "_dp_2_attr_update",
        9: "_dp_2_attr_update",  # <-- the new DP
        10: "_dp_2_attr_update",
        11: "_dp_2_attr_update",
        12: "_dp_2_attr_update",
        13: "_dp_2_attr_update",
        14: "_dp_2_attr_update",
        15: "_dp_2_attr_update",
    }

Once modified, deleted the cache and restarted HA, you can try to write the attribute value from the device vies, 'manage clusters', selecting the TemperatureHumidityManufCluster and setting the value. I'll recommend to ou, first read the value and then try to set the new one.

javicalle commented 2 years ago

Now I have a few requests:

Thanks in advanced.

abstiger commented 2 years ago

That can be fixed here:

    2: DPToAttributeMapping(
        TuyaRelativeHumidity.ep_attribute,
        "measured_value",
        dp_type=TuyaDPType.VALUE,
        converter=lambda x: x * 1000,  
    ),

Probably would be the same in DP 12 and 13.

yes, i've already changed the converter from converter=lambda x: x * 10, to converter=lambda x: x * 100, then it shows correctly.

abstiger commented 2 years ago

Can you check if the device syncs date/time correctly?

how can i check? i tried to change the device date manually to 2021/1/1, waited for some minutes, it doesn't change.

Can you check if the TemperatureHumidityManufCluster has mcu_version attribute? If not, there is any attribute with ID 0xEF00?

if i defined attributes in TemperatureHumidityManufCluster, there are no mcu_version, just some attributes i defined. 2

if i remove those attributes, i got mcu_version, but Read Attribute gets None: 3

and the other attributes you mentioned above get None also. 1

i just can get DP 1,2,4 4

javicalle commented 2 years ago

how can i check? i tried to change the device date manually to 2021/1/1, waited for some minutes, it doesn't change.

I would try removing the battery and putting it on again. I hope that the device will trigger the time sync, but the logs will tell to us.

if i defined attributes in TemperatureHumidityManufCluster, there are no mcu_version, just some attributes i defined. if i remove those attributes, i got mcu_version

I see the bug:


class TemperatureHumidityManufCluster(TuyaMCUCluster):
    """Tuya Manufacturer Cluster with Temperature and Humidity data points."""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.append({
        0xD009: ("temperature_unit_converter", t.CharacterString, True),
        .../...
blomnik commented 2 years ago

I see the bug:

class TemperatureHumidityManufCluster(TuyaMCUCluster):
    """Tuya Manufacturer Cluster with Temperature and Humidity data points."""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.append({
        0xD009: ("temperature_unit_converter", t.CharacterString, True),
        .../...

I got exception here File "/config/quirks/tuya_ts0601_tze200_whkgqxse.py", line 46, in TemperatureHumidityManufCluster attributes.append({ AttributeError: 'dict' object has no attribute 'append'

javicalle commented 2 years ago

Upsssss, try with:

class TemperatureHumidityManufCluster(TuyaMCUCluster):
    """Tuya Manufacturer Cluster with Temperature and Humidity data points."""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update({
        0xD009: ("temperature_unit_converter", t.CharacterString, True),
        .../...
    })
blomnik commented 2 years ago

Now I have a few requests:

  • Can you check if the device syncs date/time correctly?
  • Can you check if the TemperatureHumidityManufCluster has mcu_version attribute? If not, there is any attribute with ID 0xEF00?

Thanks in advanced.

I got correct values of temp, hum and battery at first time after pairing (again). But this values does not updates. How can I set update interval. Requesting value from 'report_interval (id 0xd011)' returns None. I'm stuck (

javicalle commented 2 years ago

That can be a pairing issue. Battery powered devices must keep 'awake' during all the pairing process. For that, short press the pairing button every second or so while device pairing last.

blomnik commented 2 years ago

That can be a pairing issue. Battery powered devices must keep 'awake' during all the pairing process. For that, short press the pairing button every second or so while device pairing last.

Unfortunately, this did not help. :( Same issue: got valid values first time after re-pair and no updates later. And now I see 'not available' in UI.

But, if I hold "+" and "-" buttons for a several seconds, values are become 'available', but still not updated. I guess due to 'time request' from device.

javicalle commented 1 year ago

The 'time requests part would be already fixed. Please attach the logs from your device. Any logs from command 0x0024, 0x24, 40 are wellcomed.

blomnik commented 1 year ago

The 'time requests part would be already fixed. Please attach the logs from your device. Any logs from command 0x0024, 0x24, 40 are wellcomed.

Sorry for long response. Here is my logs right after "+" and "-" pressed. By the way, Temp and Humidity began updates after last core upgrade. Time sync still does not work. May be I made something wrong?

zha.log

javicalle commented 1 year ago

Hi Arthur, from your logs it seems that there is something wrong in the quirk but not sure if related. Can you post here your quirk version? Put the code between ``` marks to format the code:

All your quirk here

blomnik commented 1 year ago

@javicalle , thank you for reply. I guess, I coded something wrong, but can't find where I failed :). Here is my quirk

"""Tuya temp and humidity sensor"""

from typing import Dict

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.measurement import RelativeHumidity, TemperatureMeasurement

from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
    SKIP_CONFIGURATION,
)
from zhaquirks.tuya import TuyaLocalCluster, TuyaPowerConfigurationCluster2AAA
from zhaquirks.tuya.ts0201 import TuyaTemperatureHumidityAlarmCluster
from zhaquirks.tuya.mcu import DPToAttributeMapping, EnchantedDevice, TuyaDPType, TuyaMCUCluster

class TuyaTemperatureMeasurement(TemperatureMeasurement, TuyaLocalCluster):
    """Tuya local TemperatureMeasurement cluster."""

class TuyaRelativeHumidity(RelativeHumidity, TuyaLocalCluster):
    """Tuya local RelativeHumidity cluster."""

class TemperatureHumidityManufCluster(TuyaMCUCluster):
    """Tuya Manufacturer Cluster with Temperature and Humidity data points."""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update({
        0xD009: ("temperature_unit_converter", t.CharacterString, True),
        # Alarm information
        0xD013: ("temperature_sensitivity", t.CharacterString, True),
        0xD011: ("report_interval", t.uint16_t, True),
        # Unknown
        0xD010: ("unknown", t.uint8_t, True),
    })

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        1: DPToAttributeMapping(
            TuyaTemperatureMeasurement.ep_attribute,
            "measured_value",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 10,  # decidegree to centidegree
        ),
        2: DPToAttributeMapping(
            TuyaRelativeHumidity.ep_attribute,
            "measured_value",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 100,  
        ),
        4: DPToAttributeMapping(
            TuyaPowerConfigurationCluster2AAA.ep_attribute,
            "battery_percentage_remaining",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 2,  # double reported percentage
        ),
        10: DPToAttributeMapping(
            TuyaTemperatureHumidityAlarmCluster.ep_attribute,
            "alarm_temperature_max",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 10,  # decipercent to centipercent
        ),
        11: DPToAttributeMapping(
            TuyaTemperatureHumidityAlarmCluster.ep_attribute,
            "alarm_temperature_min",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 10,  # decipercent to centipercent
        ),
        12: DPToAttributeMapping(
            TuyaTemperatureHumidityAlarmCluster.ep_attribute,
            "alarm_humidity_max",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 10,  # decipercent to centipercent
        ),
        13: DPToAttributeMapping(
            TuyaTemperatureHumidityAlarmCluster.ep_attribute,
            "alarm_humidity_min",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 10,  # decipercent to centipercent
        ),
        14: DPToAttributeMapping(
            TuyaTemperatureHumidityAlarmCluster.ep_attribute,
            "alarm_temperature",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x,
        ),
        15: DPToAttributeMapping(
            TuyaTemperatureHumidityAlarmCluster.ep_attribute,
            "alarm_humidity",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x,
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        4: "_dp_2_attr_update",
        10: "_dp_2_attr_update",
        11: "_dp_2_attr_update",
        12: "_dp_2_attr_update",
        13: "_dp_2_attr_update",
        14: "_dp_2_attr_update",
        15: "_dp_2_attr_update",
    }

class TuyaTempHumiditySensor(EnchantedDevice, CustomDevice):
    """Custom device representing tuya temp and humidity sensor with e-ink screen."""

    signature = {
        # <SimpleDescriptor endpoint=1, profile=260, device_type=81
        # device_version=1
        # input_clusters=[4, 5, 61184, 0]
        # output_clusters=[25, 10]>
        MODELS_INFO: [("_TZE200_whkgqxse", "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,
                    TemperatureHumidityManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
            }
        },
    }

    replacement = {
        SKIP_CONFIGURATION: True,
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR,
                INPUT_CLUSTERS: [
                    TemperatureHumidityManufCluster,  # Single bus for temp, humidity, and battery
                    TuyaTemperatureHumidityAlarmCluster,
                    TuyaTemperatureMeasurement,
                    TuyaRelativeHumidity,
                    TuyaPowerConfigurationCluster2AAA,
                ],
                OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
            }
        },
    }
javicalle commented 1 year ago

Sorry for the late response. The issue went to the backlog and I lost the track of it.

I have reviewed the issue and I believe that I have found the issue. That's the new proposed quirk:

ts0601_sensor2.py ```python """Tuya temp and humidity sensor""" from typing import Dict 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.measurement import RelativeHumidity, TemperatureMeasurement from zhaquirks.const import ( DEVICE_TYPE, ENDPOINTS, INPUT_CLUSTERS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, SKIP_CONFIGURATION, ) from zhaquirks.tuya import TuyaLocalCluster, TuyaPowerConfigurationCluster2AAA from zhaquirks.tuya.ts0201 import TuyaTemperatureHumidityAlarmCluster from zhaquirks.tuya.mcu import ( DPToAttributeMapping, EnchantedDevice, TuyaDPType, TuyaMCUCluster, ) class TuyaTemperatureMeasurement(TemperatureMeasurement, TuyaLocalCluster): """Tuya local TemperatureMeasurement cluster.""" class TuyaRelativeHumidity(RelativeHumidity, TuyaLocalCluster): """Tuya local RelativeHumidity cluster.""" class TuyaTHAC(TuyaTemperatureHumidityAlarmCluster): """Tuya cluster.""" ep_attribute = "tuya_temp_hum_alarm_cluster" class TemperatureHumidityManufCluster(TuyaMCUCluster): """Tuya Manufacturer Cluster with Temperature and Humidity data points.""" attributes = TuyaMCUCluster.attributes.copy() attributes.update( { 0xD009: ("temperature_unit_converter", t.CharacterString, True), # Alarm information 0xD013: ("temperature_sensitivity", t.CharacterString, True), 0xD011: ("report_interval", t.uint16_t, True), # Unknown 0xD010: ("unknown", t.uint8_t, True), } ) dp_to_attribute: Dict[int, DPToAttributeMapping] = { 1: DPToAttributeMapping( TuyaTemperatureMeasurement.ep_attribute, "measured_value", dp_type=TuyaDPType.VALUE, converter=lambda x: x * 10, # decidegree to centidegree ), 2: DPToAttributeMapping( TuyaRelativeHumidity.ep_attribute, "measured_value", dp_type=TuyaDPType.VALUE, converter=lambda x: x * 100, ), 4: DPToAttributeMapping( TuyaPowerConfigurationCluster2AAA.ep_attribute, "battery_percentage_remaining", dp_type=TuyaDPType.VALUE, converter=lambda x: x * 2, # double reported percentage ), 10: DPToAttributeMapping( TuyaTHAC.ep_attribute, "alarm_temperature_max", dp_type=TuyaDPType.VALUE, converter=lambda x: x * 10, # decipercent to centipercent ), 11: DPToAttributeMapping( TuyaTHAC.ep_attribute, "alarm_temperature_min", dp_type=TuyaDPType.VALUE, converter=lambda x: x * 10, # decipercent to centipercent ), 12: DPToAttributeMapping( TuyaTHAC.ep_attribute, "alarm_humidity_max", dp_type=TuyaDPType.VALUE, converter=lambda x: x * 10, # decipercent to centipercent ), 13: DPToAttributeMapping( TuyaTHAC.ep_attribute, "alarm_humidity_min", dp_type=TuyaDPType.VALUE, converter=lambda x: x * 10, # decipercent to centipercent ), 14: DPToAttributeMapping( TuyaTHAC.ep_attribute, "temperature_humidity", dp_type=TuyaDPType.VALUE, converter=lambda x: x, ), 15: DPToAttributeMapping( TuyaTHAC.ep_attribute, "alarm_humidity", dp_type=TuyaDPType.VALUE, converter=lambda x: x, ), } data_point_handlers = { 1: "_dp_2_attr_update", 2: "_dp_2_attr_update", 4: "_dp_2_attr_update", 10: "_dp_2_attr_update", 11: "_dp_2_attr_update", 12: "_dp_2_attr_update", 13: "_dp_2_attr_update", 14: "_dp_2_attr_update", 15: "_dp_2_attr_update", } class TuyaTempHumiditySensor(EnchantedDevice, CustomDevice): """Custom device representing tuya temp and humidity sensor with e-ink screen.""" signature = { # MODELS_INFO: [("_TZE200_whkgqxse", "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, TemperatureHumidityManufCluster.cluster_id, ], OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id], } }, } replacement = { SKIP_CONFIGURATION: True, ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR, INPUT_CLUSTERS: [ TemperatureHumidityManufCluster, # Single bus for temp, humidity, and battery TuyaTHAC, TuyaTemperatureMeasurement, TuyaRelativeHumidity, TuyaPowerConfigurationCluster2AAA, ], OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id], } }, } ```
b2un0 commented 1 year ago

I have the same sensor.

With the deCONZ REST API, the device worked perfectly.

Now I have switched from deCONZ to ZHA (still using the Conbee), and the device no longer provides any data.

The last quirk from the end of November does ensure that the temperature and humidity sensor is now displayed, but without data.

I have removed and added the device several times, but the result is still the same: The device is added when pairing with both sensors, but after that there is no more communication.

The time is also no longer synchronised.


pairing logs will be added later

javicalle commented 1 year ago

Logs from the device reporting are also welcomed. Enable the debug logs and look for your NWK device in the logs.

b2un0 commented 1 year ago

OK, strange behaviour.

after pairing again, the sensor data is now displayed with your quirk.

Log attached, is that enough? is something missing?

0x308f.log

image

clock sync still not work, but i think this is still related to #1737

Device: https://www.aliexpress.com/item/1005005012094422.html

blomnik commented 1 year ago

Confirm. Time sync still does not work. But I see correct value in log while sending 'set_time' packet zha.log

BTW, comment in MCUVersionRsp source tells me, that 'set_time_request' always has zero length payload. But in logs I've attached you can see 2-byte payload. Another one proprietary values?

javicalle commented 1 year ago

Device don't complain about the command so I will sipose that the command is fine. Looking at Z2M maybe the problem is in the data so I would try with a diferent set_time_offset:

class TemperatureHumidityManufCluster(TuyaMCUCluster):
    """Tuya Manufacturer Cluster with Temperature and Humidity data points."""

    set_time_offset = 2000  # possible values: 0 | 1970* | 2000)
    # set_time_local_offset = 1970

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            .../...
blomnik commented 1 year ago

Unfortunately, this does not help.

javicalle commented 1 year ago

Has the device response changed? Have you tested the other possible values?

blomnik commented 1 year ago

Yes, I saw amount of seconds from 1/1/2000, for example. To uint32_t (big-endian): one for UTC, one for my local timezone. But time on device does not updates.

b2un0 commented 1 year ago

@javicalle can you explain where set_time_offset should be used?

i can't find any usage of this variable.

here we can find the deCONZ implementation for 0x24 which worked fined for me before moving to ZHA https://github.com/manup/deconz-rest-plugin/blob/master/tuya.cpp#L82

javicalle commented 1 year ago

@javicalle can you explain where set_time_offset should be used?

The attribute is defined (and used) here: https://github.com/zigpy/zha-device-handlers/blob/5b81cd0b723fe2171a7646340a1a6a142a7a462e/zhaquirks/tuya/__init__.py#L290-L298

We are overwriting it in our class. You can assign diferents values to test.

here we can find the deCONZ implementation for 0x24 which worked fined for me before moving to ZHA

I'll check if I can find the right time format.

javicalle commented 1 year ago

I can't get how is Deconz doing the time initiallizations, but it's similar to our implementation: Deconz: https://github.com/manup/deconz-rest-plugin/blob/8fafc588522a9363f9140d39096948796b52f563/tuya.cpp#L111-L121

ZHA: https://github.com/zigpy/zha-device-handlers/blob/5b81cd0b723fe2171a7646340a1a6a142a7a462e/zhaquirks/tuya/__init__.py#L423-L428

Maybe with the raw values from Deconz we can validate it against ZHA. I don't know another way to validate if the value is the right one.

javicalle commented 1 year ago

Ummm, another crazy test could be to update the server_commands like that:

class TemperatureHumidityManufCluster(TuyaMCUCluster):
    """Tuya Manufacturer Cluster with Temperature and Humidity data points."""

    set_time_offset = 2000  # possible values: 0 | 1970* | 2000)
    # set_time_local_offset = 1970

    self.server_commands[TUYA_SET_TIME] = foundation.ZCLCommandDef(
        "set_time", {"time": TuyaTimePayload}, False, is_manufacturer_specific=False
    ),

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            .../...

After every code change you need to save changes, delete any __pycache__ folder and restart HA.

MattWestb commented 1 year ago

@javicalle Is the MCU requesting time with DP commands ? I was thinking taking the battery out and putting it back and see if it requesting time with DP command and the quirk is sending the replay for setting it.

(MCU is normal requesting time and cant being set if its not requested if i have understanding the TRVs right)

b2un0 commented 1 year ago
File "/config/zha_quirks/tuya_ts0601_tze200_whkgqxse.py", line 50, in TemperatureHumidityManufCluster
server_commands[TUYA_SET_TIME] = foundation.ZCLCommandDef(
NameError: name 'foundation' is not defined
javicalle commented 1 year ago
foundation

You will need to add at the beginning of file the imports (the zhaquirks.tuya import already exists, update it):

from zigpy.zcl import foundation
from zhaquirks.tuya import TuyaLocalCluster, TuyaPowerConfigurationCluster2AAA, TuyaTimePayload, TUYA_SET_TIME
javicalle commented 1 year ago

@javicalle Is the MCU requesting time with DP commands ?

Yes, it does:

2023-01-31 02:24:57.033 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=61184, SrcAddr=0x583E, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=96, SecurityUse=<Bool.false: 0>, TimeStamp=14388430, TSN=0, Data=b'\x09\xB8\x24\x25\x10', MacSrcAddr=0x9A57, MsgResultRadius=28)
2023-01-31 02:24:57.038 DEBUG (MainThread) [zigpy.application] Received a packet: ZigbeePacket(src=AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0x583E), src_ep=1, dst=AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0x0000), dst_ep=1, source_route=None, extended_timeout=False, tsn=0, profile_id=260, cluster_id=61184, data=Serialized[b'\t\xb8$%\x10'], tx_options=<TransmitOptions.NONE: 0>, radius=28, non_member_radius=0, lqi=96, rssi=None)
2023-01-31 02:24:57.040 DEBUG (MainThread) [zigpy.zcl] [0x583E:1:0xef00] Received ZCL frame: b'\t\xb8$%\x10'
2023-01-31 02:24:57.043 DEBUG (MainThread) [zigpy.zcl] [0x583E:1:0xef00] Decoded ZCL frame header: ZCLHeader(frame_control=FrameControl(frame_type=<FrameType.CLUSTER_COMMAND: 1>, is_manufacturer_specific=0, direction=<Direction.Client_to_Server: 1>, disable_default_response=0, reserved=0, *is_cluster=True, *is_general=False, *is_reply=True), tsn=184, command_id=36, *direction=<Direction.Client_to_Server: 1>, *is_reply=True)
2023-01-31 02:24:57.047 DEBUG (MainThread) [zigpy.zcl] [0x583E:1:0xef00] Decoded ZCL frame: TemperatureHumidityManufCluster:set_time_request(data=[37, 16])
2023-01-31 02:24:57.048 DEBUG (MainThread) [zigpy.zcl] [0x583E:1:0xef00] Received command 0x24 (TSN 184): set_time_request(data=[37, 16])
2023-01-31 02:24:57.050 DEBUG (MainThread) [zigpy.zcl] [0x583E:1:0xef00] handle_set_time_request payload: [37, 16]
2023-01-31 02:24:57.051 DEBUG (MainThread) [zigpy.zcl] [0x583E:1:0xef00] handle_set_time_request response: [99, 216, 95, 217, 99, 216, 123, 249]
2023-01-31 02:24:57.054 DEBUG (MainThread) [zigpy.zcl] [0x583E:1:0xef00] Sending request header: ZCLHeader(frame_control=FrameControl(frame_type=<FrameType.CLUSTER_COMMAND: 1>, is_manufacturer_specific=True, direction=<Direction.Server_to_Client: 0>, disable_default_response=0, reserved=0, *is_cluster=True, *is_general=False, *is_reply=False), manufacturer=4417, tsn=199, command_id=36, *direction=<Direction.Server_to_Client: 0>, *is_reply=False)
2023-01-31 02:24:57.056 DEBUG (MainThread) [zigpy.zcl] [0x583E:1:0xef00] Sending request: set_time(time=[99, 216, 95, 217, 99, 216, 123, 249])
2023-01-31 02:24:57.059 DEBUG (MainThread) [zigpy_znp.zigbee.application] Sending packet ZigbeePacket(src=AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0x0000), src_ep=1, dst=AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0x583E), dst_ep=1, source_route=None, extended_timeout=False, tsn=199, profile_id=260, cluster_id=61184, data=Serialized[b'\x05A\x11\xc7$\x00\x08c\xd8_\xd9c\xd8{\xf9'], tx_options=<TransmitOptions.ACK: 1>, radius=0, non_member_radius=0, lqi=None, rssi=None)
2023-01-31 02:24:57.063 DEBUG (MainThread) [zigpy.zcl] [0x583E:1:0xef00] Sending reply header: ZCLHeader(frame_control=FrameControl(frame_type=<FrameType.GLOBAL_COMMAND: 0>, is_manufacturer_specific=False, direction=<Direction.Client_to_Server: 1>, disable_default_response=1, reserved=0, *is_cluster=False, *is_general=True, *is_reply=True), tsn=184, command_id=<GeneralCommand.Default_Response: 11>, *direction=<Direction.Client_to_Server: 1>, *is_reply=True)
2023-01-31 02:24:57.065 DEBUG (MainThread) [zigpy.zcl] [0x583E:1:0xef00] Sending reply: Default_Response(command_id=36, status=<Status.SUCCESS: 0>)
2023-01-31 02:24:57.067 DEBUG (MainThread) [zigpy_znp.zigbee.application] Sending packet ZigbeePacket(src=AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0x0000), src_ep=1, dst=AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0x583E), dst_ep=1, source_route=None, extended_timeout=False, tsn=184, profile_id=260, cluster_id=61184, data=Serialized[b'\x18\xb8\x0b$\x00'], tx_options=<TransmitOptions.ACK: 1>, radius=0, non_member_radius=0, lqi=None, rssi=None)
2023-01-31 02:24:57.073 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x583E), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=61184, TSN=199, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK|ACK_REQUEST: 48>, Radius=0, Data=b'\x05\x41\x11\xC7\x24\x00\x08\x63\xD8\x5F\xD9\x63\xD8\x7B\xF9')
2023-01-31 02:24:57.103 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0x583E), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=61184, TSN=184, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK|ACK_REQUEST: 48>, Radius=0, Data=b'\x18\xB8\x0B\x24\x00')
2023-01-31 02:24:57.254 DEBUG (MainThread) [zigpy_znp.api] Received command: AF.IncomingMsg.Callback(GroupId=0x0000, ClusterId=61184, SrcAddr=0x583E, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=<Bool.false: 0>, LQI=96, SecurityUse=<Bool.false: 0>, TimeStamp=14402159, TSN=0, Data=b'\x18\xC7\x0B\x24\x83', MacSrcAddr=0x9A57, MsgResultRadius=28)
2023-01-31 02:24:57.258 DEBUG (MainThread) [zigpy.application] Received a packet: ZigbeePacket(src=AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0x583E), src_ep=1, dst=AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0x0000), dst_ep=1, source_route=None, extended_timeout=False, tsn=0, profile_id=260, cluster_id=61184, data=Serialized[b'\x18\xc7\x0b$\x83'], tx_options=<TransmitOptions.NONE: 0>, radius=28, non_member_radius=0, lqi=96, rssi=None)
2023-01-31 02:24:57.259 DEBUG (MainThread) [zigpy.zcl] [0x583E:1:0xef00] Received ZCL frame: b'\x18\xc7\x0b$\x83'
2023-01-31 02:24:57.261 DEBUG (MainThread) [zigpy.zcl] [0x583E:1:0xef00] Decoded ZCL frame header: ZCLHeader(frame_control=FrameControl(frame_type=<FrameType.GLOBAL_COMMAND: 0>, is_manufacturer_specific=0, direction=<Direction.Client_to_Server: 1>, disable_default_response=1, reserved=0, *is_cluster=False, *is_general=True, *is_reply=True), tsn=199, command_id=11, *direction=<Direction.Client_to_Server: 1>, *is_reply=True)

And we are sending the set_time command but not sure if the data is correct or not.

b2un0 commented 1 year ago

"""Tuya temp and humidity sensor"""

from typing import Dict

from zigpy.zcl import foundation
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.measurement import RelativeHumidity, TemperatureMeasurement

from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
    SKIP_CONFIGURATION,
)

from zhaquirks.tuya import TuyaLocalCluster, TuyaPowerConfigurationCluster2AAA, TuyaTimePayload
from zhaquirks.tuya.ts0201 import TuyaTemperatureHumidityAlarmCluster
from zhaquirks.tuya.mcu import (
    DPToAttributeMapping,
    EnchantedDevice,
    TuyaDPType,
    TuyaMCUCluster,
)

class TuyaTemperatureMeasurement(TemperatureMeasurement, TuyaLocalCluster):
    """Tuya local TemperatureMeasurement cluster."""

class TuyaRelativeHumidity(RelativeHumidity, TuyaLocalCluster):
    """Tuya local RelativeHumidity cluster."""

class TuyaTHAC(TuyaTemperatureHumidityAlarmCluster):
    """Tuya cluster."""

    ep_attribute = "tuya_temp_hum_alarm_cluster"

class TemperatureHumidityManufCluster(TuyaMCUCluster):
    """Tuya Manufacturer Cluster with Temperature and Humidity data points."""

    set_time_offset = 2000  # possible values: 0 | 1970* | 2000)
    # set_time_local_offset = 1970

    server_commands[TUYA_SET_TIME] = foundation.ZCLCommandDef(
        "set_time", {"time": TuyaTimePayload}, False, is_manufacturer_specific=False
    ),

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(

..../.....

looks like something more is missing

 File "/config/zha_quirks/tuya_ts0601_tze200_whkgqxse.py", line 46, in <module>
    class TemperatureHumidityManufCluster(TuyaMCUCluster):
  File "/config/zha_quirks/tuya_ts0601_tze200_whkgqxse.py", line 52, in TemperatureHumidityManufCluster
    server_commands[TUYA_SET_TIME] = foundation.ZCLCommandDef(
NameError: name 'server_commands' is not defined
javicalle commented 1 year ago

Ummmm, I have just decoded the last message after sending the command and it seems that device don't like it:

Default_Response(command_id=36, status=<Status.UNSUP_MANUF_CLUSTER_COMMAND: 131>)

That reminds me to the 'no_manufacturer_device'.

javicalle commented 1 year ago

looks like something more is missing

 File "/config/zha_quirks/tuya_ts0601_tze200_whkgqxse.py", line 46, in <module>
    class TemperatureHumidityManufCluster(TuyaMCUCluster):
  File "/config/zha_quirks/tuya_ts0601_tze200_whkgqxse.py", line 52, in TemperatureHumidityManufCluster
    server_commands[TUYA_SET_TIME] = foundation.ZCLCommandDef(
NameError: name 'server_commands' is not defined

try with:

    self.server_commands[TUYA_SET_TIME] = foundation.ZCLCommandDef(
        "set_time", {"time": TuyaTimePayload}, False, is_manufacturer_specific=False
    ),

(The self.server_commands part)

b2un0 commented 1 year ago

thanks, but got the same error :(

yes is deleted the __pycache__ Folder and double checked the file content with vim and nano :D

File "/config/zha_quirks/tuya_ts0601_tze200_whkgqxse.py", line 52, in TemperatureHumidityManufCluster
    server_commands[TUYA_SET_TIME] = foundation.ZCLCommandDef(
NameError: name 'server_commands' is not defined

Home Assistant 2023.2.0

javicalle commented 1 year ago

Ummm, the log must show something like

File "/config/zha_quirks/tuya_ts0601_tze200_whkgqxse.py", line 52, in TemperatureHumidityManufCluster
    self.server_commands[TUYA_SET_TIME] = foundation.ZCLCommandDef(
NameError: name 'server_commands' is not defined

There is something left. Is the line 52 the new one?

b2un0 commented 1 year ago
File "/config/zha_quirks/tuya_ts0601_tze200_whkgqxse.py", line 52, in TemperatureHumidityManufCluster
    self.server_commands[TUYA_SET_TIME] = foundation.ZCLCommandDef(
NameError: name 'self' is not defined
javicalle commented 1 year ago

Arrrrrrgh....


class TemperatureHumidityManufCluster(TuyaMCUCluster):
    """Tuya Manufacturer Cluster with Temperature and Humidity data points."""

    set_time_offset = 2000  # possible values: 0 | 1970* | 2000)
    # set_time_local_offset = 1970

    server_commands = TuyaMCUCluster.server_commands.copy()
    server_commands[TUYA_SET_TIME] = foundation.ZCLCommandDef(
        "set_time", {"time": TuyaTimePayload}, False, is_manufacturer_specific=False
    )

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            .../...