zigpy / zha-device-handlers

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

[Device Support Request] Tuya Smart Air Detector 6 in 1 #1563

Open julianjwong opened 2 years ago

julianjwong commented 2 years ago

Is your feature request related to a problem? Please describe. Current device support for the Tuya Air Detector 6 in 1 is limited to only temperature and VOC sensors. Other sensors such as PM2.5 and CO2 are missing. This is the device https://zigbee.blakadder.com/Tuya_DCR-KQG.html Supposedly working on Z2M

Describe the solution you'd like Would be great if other sensor entities could be added especially the PM2.5 sensor

Device signature - this can be acquired by clicking on the "Zigbee Device Signature" button in the device settings ``` { "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=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": "0x0100", "in_clusters": [ "0x0000", "0x0004", "0x0005", "0x0402", "0x0405", "0x040d", "0x042b", "0x042e", "0xef00" ], "out_clusters": [ "0x000a", "0x0019" ] }, "242": { "profile_id": 41440, "device_type": "0x0061", "in_clusters": [], "out_clusters": [ "0x0021" ] } }, "manufacturer": "_TZE200_dwcarsat", "model": "TS0601", "class": "zhaquirks.tuya.air.ts0601_air_quality.TuyaCO2SensorGPP" } ```
Diagnostic information - this can be acquired by clicking on the "Download Diagnostics" button in the device settings ``` { "home_assistant": { "installation_type": "Home Assistant OS", "version": "2022.5.4", "dev": false, "hassio": true, "virtualenv": false, "python_version": "3.9.9", "docker": true, "arch": "x86_64", "timezone": "Australia/Sydney", "os_name": "Linux", "os_version": "5.10.108", "supervisor": "2022.05.1", "host_os": "Home Assistant OS 7.6", "docker_version": "20.10.9", "chassis": "vm", "run_as_root": true }, "custom_components": { "nest_protect": { "version": "0.3.7", "requirements": [] }, "samsungtv_tizen": { "version": "1.5.9", "requirements": [ "websocket-client==0.56.0", "wakeonlan==1.1.6", "numpy==1.21.1" ] }, "xiaomi_miot": { "version": "0.6.4", "requirements": [ "construct==2.10.56", "python-miio>=0.5.6", "micloud>=0.3" ] }, "zha_toolkit": { "version": "v0.8.8", "requirements": [] }, "powerpal": { "version": "0.1.0", "requirements": [ "mindmelting.powerpal==0.3.0" ] }, "powercalc": { "version": "v0.19.17", "requirements": [ "numpy>=1.21.1" ] }, "localtuya": { "version": "3.2.1", "requirements": [] }, "xiaomi_cloud_map_extractor": { "version": "v2.1.5", "requirements": [ "pillow", "pybase64", "python-miio", "requests", "pycryptodome" ] }, "hacs": { "version": "1.24.5", "requirements": [ "aiogithubapi>=21.11.0" ] }, "echonetlite": { "version": "3.4.9", "requirements": [ "pychonet==2.2.1", "aio-udp-server" ] }, "bureau_of_meteorology": { "version": "1.1.2", "requirements": [] }, "philips_airpurifier_coap": { "version": "0.10.6", "requirements": [ "aioairctrl==0.2.3" ] }, "ble_monitor": { "version": "8.6.5", "requirements": [ "pycryptodomex>=3.14.1", "janus>=1.0.0", "aioblescan>=0.2.12", "btsocket>=0.2.0", "pyric>=0.1.6.3" ] }, "wiz_light": { "version": "0.4.5", "requirements": [ "pywizlight==0.4.15" ] }, "hnap_device": { "version": "0.2.0", "requirements": [ "hnap>=1.0.0a3,<2.0.0" ] }, "philips_airpurifier": { "version": "1.0.1", "requirements": [ "pycryptodome" ] }, "triones": { "version": "0.0.1", "requirements": [ "bleak==0.13.0" ] } }, "integration_manifest": { "domain": "zha", "name": "Zigbee Home Automation", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ "bellows==0.29.0", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.73", "zigpy-deconz==0.16.0", "zigpy==0.45.1", "zigpy-xbee==0.14.0", "zigpy-zigate==0.7.4", "zigpy-znp==0.7.0" ], "usb": [ { "vid": "10C4", "pid": "EA60", "description": "*2652*", "known_devices": [ "slae.sh cc2652rb stick" ] }, { "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": "10C4", "pid": "8B34", "description": "*bv 2010/10*", "known_devices": [ "Bitron Video AV2010/10" ] } ], "codeowners": [ "@dmulcahey", "@adminiuga" ], "zeroconf": [ { "type": "_esphomelib._tcp.local.", "name": "tube*" } ], "after_dependencies": [ "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": 21487, "manufacturer": "_TZE200_dwcarsat", "model": "TS0601", "name": "_TZE200_dwcarsat TS0601", "quirk_applied": true, "quirk_class": "zhaquirks.tuya.air.ts0601_air_quality.TuyaCO2SensorGPP", "manufacturer_code": 4417, "power_source": "Mains", "lqi": 47, "rssi": null, "last_seen": "2022-05-14T10:51:31", "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=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=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": "0x0100", "in_clusters": [ "0x0000", "0x0004", "0x0005", "0x0402", "0x0405", "0x040d", "0x042b", "0x042e", "0xef00" ], "out_clusters": [ "0x000a", "0x0019" ] }, "242": { "profile_id": 41440, "device_type": "0x0061", "in_clusters": [], "out_clusters": [ "0x0021" ] } } }, "entities": [ { "entity_id": "sensor.air_quality_sensor_temperature", "name": "_TZE200_dwcarsat TS0601" }, { "entity_id": "sensor.tze200_dwcarsat_ts0601_0ca13a38_carbon_dioxide_concentration", "name": "_TZE200_dwcarsat TS0601" }, { "entity_id": "sensor.tze200_dwcarsat_ts0601_0ca13a38_formaldehyde_concentration", "name": "_TZE200_dwcarsat TS0601" }, { "entity_id": "sensor.tze200_dwcarsat_ts0601_0ca13a38_humidity", "name": "_TZE200_dwcarsat TS0601" }, { "entity_id": "sensor.air_quality_sensor_voc_level", "name": "_TZE200_dwcarsat TS0601" } ], "neighbors": [ { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Parent", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xDBB0", "permit_joining": "Unknown", "depth": "1", "lqi": "42" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x5B3B", "permit_joining": "Unknown", "depth": "1", "lqi": "81" }, { "device_type": "Coordinator", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x0000", "permit_joining": "Unknown", "depth": "0", "lqi": "60" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x2567", "permit_joining": "Unknown", "depth": "2", "lqi": "85" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xE219", "permit_joining": "Unknown", "depth": "3", "lqi": "66" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x6FBD", "permit_joining": "Unknown", "depth": "2", "lqi": "69" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xEE9C", "permit_joining": "Unknown", "depth": "1", "lqi": "12" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x6CA6", "permit_joining": "Unknown", "depth": "2", "lqi": "60" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xA873", "permit_joining": "Unknown", "depth": "1", "lqi": "33" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x22B2", "permit_joining": "Unknown", "depth": "1", "lqi": "27" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x2ADC", "permit_joining": "Unknown", "depth": "1", "lqi": "33" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xBDDE", "permit_joining": "Unknown", "depth": "1", "lqi": "60" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x882D", "permit_joining": "Unknown", "depth": "1", "lqi": "24" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x4C1E", "permit_joining": "Unknown", "depth": "1", "lqi": "30" } ], "endpoint_names": [ { "name": "ON_OFF_LIGHT" }, { "name": "unknown 97 device_type of 0xa1e0 profile id" } ], "user_given_name": "Air quality sensor", "device_reg_id": "7746e4f45890650bf6cf8d5f6db31638", "area_id": "toy_room" } } ```
Additional logs ``` Paste any additional debug logs here. ```

Additional context Add any other context or screenshots about the feature request here.

javicalle commented 2 years ago

The device has been tunned in zigbee-herdsman-converters library:

This could be a quirk for the device:

ts0601_air_quality.py ```python """Tuya Air Quality sensor.""" from typing import Dict from zigpy.profiles import zha from zigpy.quirks import CustomDevice from zigpy.zcl.clusters.general import Basic, GreenPowerProxy, Groups, Ota, Scenes, Time from zigpy.zcl.clusters.measurement import PM25 from zhaquirks.const import ( DEVICE_TYPE, ENDPOINTS, INPUT_CLUSTERS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, ) from zhaquirks.tuya import DPToAttributeMapping, TuyaLocalCluster, TuyaNewManufCluster from zhaquirks.tuya.air import ( TuyaAirQualityCO2, TuyaAirQualityFormaldehyde, TuyaAirQualityHumidity, TuyaAirQualityTemperature, TuyaAirQualityVOC, TuyaCO2ManufCluster, ) class TuyaCO2Sensor(CustomDevice): """Tuya Air quality device.""" signature = { # NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)] # device_version=1 # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=81, device_version=1, # input_clusters=[0, 4, 5, 61184], # output_clusters=[25, 10]) MODELS_INFO: [ ("_TZE200_8ygsuhe1", "TS0601"), ("_TZE200_yvx5lh6k", "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, TuyaCO2ManufCluster.cluster_id, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], } }, } replacement = { ENDPOINTS: { 1: { DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT, INPUT_CLUSTERS: [ Basic.cluster_id, Groups.cluster_id, Scenes.cluster_id, TuyaCO2ManufCluster, TuyaAirQualityCO2, TuyaAirQualityFormaldehyde, TuyaAirQualityHumidity, TuyaAirQualityTemperature, TuyaAirQualityVOC, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], } } } class TuyaCO2SensorGPP(CustomDevice): """Tuya Air quality device with GPP.""" signature = { # NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)] # device_version=1 # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=81, device_version=1, # input_clusters=[0, 4, 5, 61184], # output_clusters=[25, 10]) MODELS_INFO: [ ("_TZE200_ryfmq5rl", "TS0601"), ("_TZE200_yvx5lh6k", "TS0601"), ("_TZE200_dwcarsat", "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, TuyaCO2ManufCluster.cluster_id, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], }, 242: { # , complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)] # device_version=1 # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=81, device_version=1, # input_clusters=[0, 4, 5, 61184], # output_clusters=[25, 10]) MODELS_INFO: [ ("_TZE200_dwcarsat", "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, TuyaCO2ManufCluster.cluster_id, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], }, 242: { #

Conversion factors can be wrong. PM25 has a filter value not tested before:

lambda x: None if (value === 0xaaac || value === 0xaaab) else x,
julianjwong commented 2 years ago

Sorry looks like the PM2.5 sensor code is not right.

Logger: homeassistant.config_entries
Source: components/zha/__init__.py:99
First occurred: 12:15:54 AM (1 occurrences)
Last logged: 12:15:54 AM

Error setting up entry Sonoff Zigbee 3.0 USB Dongle Plus, s/n: 0a28bcebc512ec11892b21c7bd930c07 - ITead for zha
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 335, in async_setup
    result = await component.async_setup_entry(hass, self)
  File "/usr/src/homeassistant/homeassistant/components/zha/__init__.py", line 99, in async_setup_entry
    setup_quirks(config)
  File "/usr/local/lib/python3.9/site-packages/zhaquirks/__init__.py", line 409, in setup
    importer.find_module(modname).load_module(modname)
  File "<frozen importlib._bootstrap_external>", line 529, in _check_name_wrapper
  File "<frozen importlib._bootstrap_external>", line 1029, in load_module
  File "<frozen importlib._bootstrap_external>", line 854, in load_module
  File "<frozen importlib._bootstrap>", line 274, in _load_module_shim
  File "<frozen importlib._bootstrap>", line 711, in _load
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 846, in exec_module
  File "<frozen importlib._bootstrap_external>", line 983, in get_code
  File "<frozen importlib._bootstrap_external>", line 913, in source_to_code
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/config/quirks/ts0601_air_quality.py", line 96
    lambda x: None if (value === 0xaaac || value === 0xaaab) else x,
                               ^
SyntaxError: invalid syntax
javicalle commented 2 years ago

Can you try replacing this expression?:

lambda x: None if (value == 0xaaac or value == 0xaaab) else x,
julianjwong commented 2 years ago

Thanks but new issue

Logger: homeassistant.config_entries
Source: components/zha/__init__.py:99
First occurred: 11:49:22 AM (1 occurrences)
Last logged: 11:49:22 AM

Error setting up entry Sonoff Zigbee 3.0 USB Dongle Plus, s/n: 0a28bcebc512ec11892b21c7bd930c07 - ITead for zha
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 335, in async_setup
    result = await component.async_setup_entry(hass, self)
  File "/usr/src/homeassistant/homeassistant/components/zha/__init__.py", line 99, in async_setup_entry
    setup_quirks(config)
  File "/usr/local/lib/python3.9/site-packages/zhaquirks/__init__.py", line 409, in setup
    importer.find_module(modname).load_module(modname)
  File "<frozen importlib._bootstrap_external>", line 529, in _check_name_wrapper
  File "<frozen importlib._bootstrap_external>", line 1029, in load_module
  File "<frozen importlib._bootstrap_external>", line 854, in load_module
  File "<frozen importlib._bootstrap>", line 274, in _load_module_shim
  File "<frozen importlib._bootstrap>", line 711, in _load
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 846, in exec_module
  File "<frozen importlib._bootstrap_external>", line 983, in get_code
  File "<frozen importlib._bootstrap_external>", line 913, in source_to_code
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/config/quirks/ts0601_air_quality.py", line 96
    lambda x: None if (value == 0xaaac || value == 0xaaab) else x,
                                        ^
SyntaxError: invalid syntax
julianjwong commented 2 years ago

Ok so I tried replacing || with or and that seems to have gotten it out of the errors. But for some reason the device isn't using the custom quirk. Have this in my config and I know that works hence why it was throwing errors previously

zha:
  enable_quirks: true
  custom_quirks_path: /config/quirks
javicalle commented 2 years ago

I have made a mess trying to put everything in a single file. Could you try with the new version (from the previous comment)?

Delete any __pycache__ folder in your custom_quirks_path and restart HA.

julianjwong commented 2 years ago

Cool thanks looks like it loaded this time as denoted by Quirk: ts0601_air_quality.TuyaCO2SensorGPP_2. Even the previous standard quirk loaded VOC, temperature, CO2, formaldehyde, and humidity (but didn't when I first looked) but unfortunately this custom quirk doesnt add a PM2.5 sensor entity. Should it show up automatically? Or do I need to remove the device and pair it again?

julianjwong commented 2 years ago

With the new quirk formaldehyde dropped from 360ppm to 4ppm, crazy co2 spikes may have been eliminated which is good. Just not sure if the formaldehyde reading is right

javicalle commented 2 years ago

formaldehyde dropped from 360ppm to 4ppm

You can try to change in your quirk this part:

        20: DPToAttributeMapping(
            TuyaAirQualityFormaldehyde.ep_attribute,
            "measured_value",
            lambda x: x * 1e-3,
        ),

There's any way to validate wich values are the correct ones?

The conversion factors are probably need to be revised. Tell me if there is any other value that seems strange to you.

I think that I saw that PM2.5 does not generate an entity in HA, but you should be able to query it as attributes of the TuyaAirQualityPM25 cluster.

crazy co2 spikes may have been eliminated which is good

Well, actually these spikes seems that aren't CO2 values but PM2.5 values (and had been filtered).

The modifications would be:

In Z2M there are a table with units and range values for this device:

julianjwong commented 2 years ago

Ok here's my values vs the table. Looks like some mismatched ones. 167911691-d24b952b-6c5b-4637-991e-4fce758be1f4 Screenshot_20220516-233058_Home Assistant

afaucogney commented 2 years ago

Hey guys, I bought this device too, but it even does not pair ! Have you got some issue about that by your side ?

Info 2022-05-16 21:50:00Device '0xa4c13821495fcb38' joined
Info 2022-05-16 21:50:01MQTT publish: topic 'zigbee2mqtt/bridge/event', payload '{"data":{"friendly_name":"0xa4c13821495fcb38","ieee_address":"0xa4c13821495fcb38"},"type":"device_joined"}'
Info 2022-05-16 21:50:01MQTT publish: topic 'zigbee2mqtt/bridge/log', payload '{"message":{"friendly_name":"0xa4c13821495fcb38"},"type":"device_connected"}'
Info 2022-05-16 21:50:01Starting interview of '0xa4c13821495fcb38'
Info 2022-05-16 21:50:02MQTT publish: topic 'zigbee2mqtt/bridge/event', payload '{"data":{"friendly_name":"0xa4c13821495fcb38","ieee_address":"0xa4c13821495fcb38","status":"started"},"type":"device_interview"}'
Info 2022-05-16 21:50:02MQTT publish: topic 'zigbee2mqtt/bridge/log', payload '{"message":"interview_started","meta":{"friendly_name":"0xa4c13821495fcb38"},"type":"pairing"}'
Error 2022-05-16 21:52:19Failed to interview '0xa4c13821495fcb38', device has not successfully been paired
julianjwong commented 2 years ago

Hey guys, I bought this device too, but it even does not pair ! Have you got some issue about that by your side ?

Info 2022-05-16 21:50:00Device '0xa4c13821495fcb38' joined
Info 2022-05-16 21:50:01MQTT publish: topic 'zigbee2mqtt/bridge/event', payload '{"data":{"friendly_name":"0xa4c13821495fcb38","ieee_address":"0xa4c13821495fcb38"},"type":"device_joined"}'
Info 2022-05-16 21:50:01MQTT publish: topic 'zigbee2mqtt/bridge/log', payload '{"message":{"friendly_name":"0xa4c13821495fcb38"},"type":"device_connected"}'
Info 2022-05-16 21:50:01Starting interview of '0xa4c13821495fcb38'
Info 2022-05-16 21:50:02MQTT publish: topic 'zigbee2mqtt/bridge/event', payload '{"data":{"friendly_name":"0xa4c13821495fcb38","ieee_address":"0xa4c13821495fcb38","status":"started"},"type":"device_interview"}'
Info 2022-05-16 21:50:02MQTT publish: topic 'zigbee2mqtt/bridge/log', payload '{"message":"interview_started","meta":{"friendly_name":"0xa4c13821495fcb38"},"type":"pairing"}'
Error 2022-05-16 21:52:19Failed to interview '0xa4c13821495fcb38', device has not successfully been paired

I'm using ZHA and paired ok for me? Did you try moving it closer to your coordinator?

javicalle commented 2 years ago

Apart from the temperature and humidity, the rest of the values I have no way of validating them. Values for CO2 may be in the correct range values.

It seems that the units for VOC would not be correct (the device measures in ppm and HA shows ug/m3). Maybe needs to be adjusted here:

The rest of the measures also seem to need to be adjusted. According to these tables:

values for Formaldehyde seems to be too high, and also with the diferent units. There is no alternative sensor in HA, so maybe a units conversion can be needed:

julianjwong commented 2 years ago

So I have another sensor in a different room in the same house but presumably similar readings. Here's what it shows signal-2022-05-17-20-48-32-309 Ignore temperature and humidity values as the heater is on in that room

Sesshoumaru-sama commented 2 years ago

I own the same device with the same issues (which almost feels reassuring). Wonky values for formaldehyde and no showing up of PM2.5

Sad. Hopefully we can fine a fix and get it to the ZHA component in HA.

javicalle commented 2 years ago

Ummm, it seems that CO2 and VOC values are fine (VOC: 15µg/m3 ~ 0,012mg/m3)

The measuring range of the device seems to be 0-10µg/m3 (possibly without decimals) HA assumes that Formaldehyde units are PPM with a multiplier of 1e6, and displays values without decimals:

Here I don't know what should be the correct solution. We can try to do a ug/m3 --> PPM conversion in the quirk so that the information displayed in HA is correct. Or propose a change in HA so that it also accepts PPM as units of measurement for Formaldehyde 🤷🏻‍♂️

Any suggestion is welcome.

julianjwong commented 2 years ago

I'd say that given these two sensors both have different units it's probably best to suggest a change in HA for the units?

dmulcahey commented 2 years ago

We had the same issue with VOC in HA. we can create a ppm formaldehyde entity similar to how we did for VOC

maxperron commented 2 years ago

1406

I believe it's the same devices and issues

drmulligan commented 1 year ago

1707 is also related.

julianjwong commented 1 year ago

Not sure when this happened but pm2.5 is now showing for me. Has spikes to over 40000 but generally working

vikitor87 commented 1 year ago

Today I have implemented the quirk for this sensor, and it shows everything apparently correct except PM2.5. Captura1

In the HomeAssistant logs I get this error.

Logger: homeassistant
Source: zha_quirks/ts0601_air_quality.py:158
First occurred: 19:22:42 (1529 occurrences)
Last logged: 19:35:22

Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/zigpy_znp/zigbee/application.py", line 588, in on_af_message
    self.packet_received(
  File "/usr/local/lib/python3.10/site-packages/zigpy/application.py", line 855, in packet_received
    self.handle_message(
  File "/usr/local/lib/python3.10/site-packages/zigpy/application.py", line 361, in handle_message
    return sender.handle_message(
  File "/usr/local/lib/python3.10/site-packages/zigpy/device.py", line 370, in handle_message
    return endpoint.handle_message(
  File "/usr/local/lib/python3.10/site-packages/zigpy/endpoint.py", line 224, in handle_message
    handler(hdr, args, dst_addressing=dst_addressing)
  File "/usr/local/lib/python3.10/site-packages/zigpy/zcl/__init__.py", line 373, in handle_message
    self.handle_cluster_request(hdr, args, dst_addressing=dst_addressing)
  File "/usr/local/lib/python3.10/site-packages/zhaquirks/tuya/__init__.py", line 1431, in handle_cluster_request
    status = getattr(self, handler_name)(*args)
  File "/usr/local/lib/python3.10/site-packages/zhaquirks/tuya/__init__.py", line 1449, in handle_get_data
    getattr(self, dp_handler)(record)
  File "/usr/local/lib/python3.10/site-packages/zhaquirks/tuya/__init__.py", line 1482, in _dp_2_attr_update
    value = dp_map.converter(value)
  File "/config/zha_quirks/ts0601_air_quality.py", line 158, in <lambda>
    lambda x: None if (value == 0xaaac or value == 0xaaab) else x,
NameError: name 'value' is not defined

Any clue to get the PM2.5 values? I have observed that in Z2M it is possible, and there is a thread about it. Added new model Tuya Air quality sensor TS0601 _TZE200_dwcarsat Thanks.

javicalle commented 1 year ago

Replace this:

lambda x: None if (value == 0xaaac or value == 0xaaab) else x

With:

lambda x: None if (x == 0xaaac or x == 0xaaab) else x
vikitor87 commented 1 year ago

You've got it. Now the value appears, perfect! I think it should be divided by 10, or if the value is true (from 65 to 75 ug/m3), I have a short time to live.

javicalle commented 1 year ago

Then you can try with;

lambda x: None if (x == 0xaaac or x == 0xaaab) else x/10
ylemoigne commented 1 year ago

@vikitor87 Can you share the quirk file you've implemented ?

vikitor87 commented 1 year ago

Of course! @ylemoigne . It is the same quirk file published at the beginning but modifying code line 158

ts0601_air_quality.py

``` """Tuya Air Quality sensor.""" from typing import Dict from zigpy.profiles import zha from zigpy.quirks import CustomDevice from zigpy.zcl.clusters.general import Basic, GreenPowerProxy, Groups, Ota, Scenes, Time from zigpy.zcl.clusters.measurement import PM25 from zhaquirks.const import ( DEVICE_TYPE, ENDPOINTS, INPUT_CLUSTERS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, ) from zhaquirks.tuya import DPToAttributeMapping, TuyaLocalCluster, TuyaNewManufCluster from zhaquirks.tuya.air import ( TuyaAirQualityCO2, TuyaAirQualityFormaldehyde, TuyaAirQualityHumidity, TuyaAirQualityTemperature, TuyaAirQualityVOC, TuyaCO2ManufCluster, ) class TuyaCO2Sensor(CustomDevice): """Tuya Air quality device.""" signature = { # NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)] # device_version=1 # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=81, device_version=1, # input_clusters=[0, 4, 5, 61184], # output_clusters=[25, 10]) MODELS_INFO: [ ("_TZE200_8ygsuhe1", "TS0601"), ("_TZE200_yvx5lh6k", "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, TuyaCO2ManufCluster.cluster_id, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], } }, } replacement = { ENDPOINTS: { 1: { DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT, INPUT_CLUSTERS: [ Basic.cluster_id, Groups.cluster_id, Scenes.cluster_id, TuyaCO2ManufCluster, TuyaAirQualityCO2, TuyaAirQualityFormaldehyde, TuyaAirQualityHumidity, TuyaAirQualityTemperature, TuyaAirQualityVOC, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], } } } class TuyaCO2SensorGPP(CustomDevice): """Tuya Air quality device with GPP.""" signature = { # NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)] # device_version=1 # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=81, device_version=1, # input_clusters=[0, 4, 5, 61184], # output_clusters=[25, 10]) MODELS_INFO: [ ("_TZE200_ryfmq5rl", "TS0601"), ("_TZE200_yvx5lh6k", "TS0601"), ("_TZE200_dwcarsat", "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, TuyaCO2ManufCluster.cluster_id, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], }, 242: { # , complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)] # device_version=1 # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=81, device_version=1, # input_clusters=[0, 4, 5, 61184], # output_clusters=[25, 10]) MODELS_INFO: [ ("_TZE200_dwcarsat", "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, TuyaCO2ManufCluster.cluster_id, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], }, 242: { #

· Entities

cap1 cap2

gudvinr commented 1 year ago

Excluding 0xaaac and 0xaaab is not enough, over the time you'll notice that it produces some very off singular values that will be normal on next measurement. Those values do not make any sense being way over claimed measurement range.

ylemoigne commented 1 year ago

@vikitor87 thanks, it works (well at least it looks better now, i don't know the accuracy of the device)

lonnie776 commented 1 year ago

So after much head scratching and googling, I was able to get my 6 sensors to show up using ts0601_air_quality.py from above. However now I am fairly certain Formaldehyde, PM2.5, and VOC are all incorrect and will require some other scaling factor. From my brief research my values are out of the normal for indoor conditions. I have also noticed the Unit of Measure is incorrect for VOC and Formaldehyde, but this can be fixed with Customize.

Has anyone been able to get sane readings from Tuya and compared them to the values reported by Home Assistant?

javicalle commented 1 year ago

From the various reports that I have found of this type of sensor, I think that there are at least 2 different types of sensors with mixed sensors/DPs. Look for a quirk for your exact manufacturer to be sure.

RSDynamics commented 1 year ago

Not a lot of action here but I have the TS0601 _TZE200_dwcarsat I copied this quirk as the values seems to be mixed up. Values seem to be at the right place now but the accuracy of the sensor is not very high. An almost constant humidity of 105% and CO2 level of around 362ppm are just impossible. PM25 is spiking to 4000 at random times. Is there a way to add an offset to the values? CO2 is about 100ppm to low and humidity is 40% to high.

vietpv89 commented 1 year ago

RSDynamics i have same issues, humidity allways higher 100%. Did u resolve that issue ?

RSDynamics commented 1 year ago

No, did not had the time yet. What I do think it is a different issue than a offset. The humidity stays within about 1% so of the 105% so that is also not very realistic. I think temperature is in the same component and is reliable so not sure what the issue is at the moment.

codyrocco commented 1 year ago

still no news? anyway, event on z2m, co2 values seems to be...fixed, at around 350 :))) attached are values from zha... i should be very dead now!

image

martin3000 commented 11 months ago

I added this entry to my configuration.yaml: zha: .. custom_quirks_path: .homeassistant/custom_zha_quirks/

(with HA core)

I stored the ts0601_air_quality.py in this directory. The formaldehyde values are better now, but with 40ppm outside on the balcony still to high. I think it should not be more than 0.1ppm. Maybe the unit is µg/m³ ? 1 ⁠ppm⁠ =1240 µg/m³. In this case the value should be divided by 1240 to convert from µg/m³ to ppm. But then in homeassistant/components/zha/sensor.py#L586-L594 , _decimals has to be increased.

Also I think that device has the VOC level in ppm, whereas HA display it in µg. Does it need conversion?

martin3000 commented 11 months ago

<!DOCTYPE html>

  | device unit | HA unit | conv. fact. | healthy if -- | -- | -- | -- | -- CO2 | ppm | ppm |   | <1000 ppm (outdoor: 400ppm) Formaldehyde | µg/m³ | ppm | /1240 | <0.1 ppm Pm2.5 | µg/m³ | µg/m³ |   | <50 µg/m³ VOC | ppm | µg/m³ | *2500 | <3000 µg/m³ (outdoor 100-200 µg/m³) Temperature | °C | °C |   |   Humidity | % | % |   |  
martin3000 commented 11 months ago

By comparing the values with the tuya app, I have now found the following:

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        2: DPToAttributeMapping(
            TuyaAirQualityPM25.ep_attribute, "measured_value",  lambda x: None if (x>500) else x
        ),
        18: DPToAttributeMapping(
            TuyaAirQualityTemperature.ep_attribute, "measured_value", lambda x: x * 10
        ),
        19: DPToAttributeMapping(
            TuyaAirQualityHumidity.ep_attribute, "measured_value", lambda x: x * 10
        ),
        20: DPToAttributeMapping(
           TuyaAirQualityFormaldehyde.ep_attribute, "measured_value", lambda x: x * 1e-6
        ),
        21: DPToAttributeMapping(
            TuyaAirQualityVOC.ep_attribute, "measured_value", lambda x: x * 1e-6
        ),
        22: DPToAttributeMapping(
            TuyaAirQualityCO2.ep_attribute, "measured_value", lambda x: x * 1e-6,
        ),
    }

The formaldehyde unit must be changed from ppm to ppb (customize: section).

martin3000 commented 11 months ago

VOC is displayed in the tuya app with the unit mg/m³, in the HA tuya integration it shows up as ppm. The numbers seem to be the same. If zhaquirks sends a value of 10, the tuya app shows it as 0,010 mg/m³ (would be 10 µg/m³) and the tuya HA integration shows it as 0,010 ppm (would be 25 µg/m³).

github-actions[bot] commented 5 months ago

There hasn't been any activity on this issue recently. Due to the high number of incoming GitHub notifications, we have to clean some of the old issues, as many of them have already been resolved with the latest updates. Please make sure to update to the latest version and check if that solves the issue. Let us know if that works for you by adding a comment 👍 This issue has now been marked as stale and will be closed if no further activity occurs. Thank you for your contributions.

Abdull commented 5 months ago

/unstale

frog32 commented 3 months ago

The combined work of folks in this thread already results in a much better quirk than is currently available from zha-device-handlers. Would any of the contributors mind submitting a pull request so less tech-savvy people can use it as well and are not getting garbage?