zha-device-handlers
zha-device-handlers copied to clipboard
[Device Support Request] Tuya Smart Air Detector 6 in 1
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=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4417, maximum_buffer_size=66, maximum_incoming_transfer_size=66, server_mask=10752, maximum_outgoing_transfer_size=66, descriptor_capability_field=<DescriptorCapability.NONE: 0>, *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=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4417, maximum_buffer_size=66, maximum_incoming_transfer_size=66, server_mask=10752, maximum_outgoing_transfer_size=66, descriptor_capability_field=<DescriptorCapability.NONE: 0>, *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.
The device has been tunned in zigbee-herdsman-converters
library:
- https://github.com/Koenkk/zigbee-herdsman-converters/blob/ce119a6ca14033880dca8bf83cd01bc928ab5657/converters/fromZigbee.js#L4334
This could be a quirk for the device:
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=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=<DescriptorCapability.0: 0>, *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=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=<DescriptorCapability.0: 0>, *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: {
# <SimpleDescriptor endpoint=242 profile=41440 device_type=97
# input_clusters=[]
# output_clusters=[33]
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.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],
},
242: {
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
}
}
class TuyaAirQualityPM25(PM25, TuyaLocalCluster):
"""Tuya PM25 concentration measurement."""
# ug/m^3 https://github.com/Koenkk/zigbee2mqtt/issues/11033#issuecomment-1124056947
class TuyaCO2ManufCluster_2(TuyaNewManufCluster):
"""Tuya with Air quality device v2 with data points."""
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
2: DPToAttributeMapping(
TuyaAirQualityPM25.ep_attribute,
"measured_value",
lambda x: None if (value == 0xaaac or value == 0xaaab) 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,
),
}
data_point_handlers = {
2: "_dp_2_attr_update",
18: "_dp_2_attr_update",
19: "_dp_2_attr_update",
20: "_dp_2_attr_update",
21: "_dp_2_attr_update",
22: "_dp_2_attr_update",
}
class TuyaCO2SensorGPP_2(CustomDevice):
"""Tuya Air quality device v2 with GPP."""
signature = {
# NodeDescriptor(logical_type=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=<DescriptorCapability.0: 0>, *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: {
# <SimpleDescriptor endpoint=242 profile=41440 device_type=97
# input_clusters=[]
# output_clusters=[33]
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
},
}
replacement = {
ENDPOINTS: {
1: {
DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaCO2ManufCluster_2,
TuyaAirQualityPM25,
TuyaAirQualityCO2,
TuyaAirQualityFormaldehyde,
TuyaAirQualityHumidity,
TuyaAirQualityTemperature,
TuyaAirQualityVOC,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
},
242: {
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
}
}
Conversion factors can be wrong. PM25 has a filter value not tested before:
lambda x: None if (value === 0xaaac || value === 0xaaab) else x,
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
Can you try replacing this expression?:
lambda x: None if (value == 0xaaac or value == 0xaaab) else x,
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
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
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.
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?
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
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:
- temp, hum & VOC keep unchanged
- the previous CO2 readings (with spikes) readings were actually PM2.5 readings
- the previous Formaldehyde readings were actually CO2 readings
- the current Formaldehyde readings weren't previouslly readed
In Z2M there are a table with units and range values for this device:
- https://github.com/Koenkk/zigbee2mqtt/issues/11033#issuecomment-1124056947
Ok here's my values vs the table. Looks like some mismatched ones.
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
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?
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:
- https://github.com/home-assistant/core/blob/513e276bbaba4c1d750cad4253db5c79fb341ec5/homeassistant/components/zha/sensor.py#L570-L576
The rest of the measures also seem to need to be adjusted. According to these tables:
- https://github.com/Koenkk/zigbee2mqtt/issues/11033#issuecomment-1110717475
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:
- https://github.com/home-assistant/core/blob/513e276bbaba4c1d750cad4253db5c79fb341ec5/homeassistant/components/zha/sensor.py#L586-L594
So I have another sensor in a different room in the same house but presumably similar readings.
Here's what it shows
Ignore temperature and humidity values as the heater is on in that room
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.
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:
- https://github.com/home-assistant/core/blob/513e276bbaba4c1d750cad4253db5c79fb341ec5/homeassistant/components/zha/sensor.py#L587-L594
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.
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?
We had the same issue with VOC in HA. we can create a ppm formaldehyde entity similar to how we did for VOC
#1406
I believe it's the same devices and issues
#1707 is also related.
Not sure when this happened but pm2.5 is now showing for me. Has spikes to over 40000 but generally working
Today I have implemented the quirk for this sensor, and it shows everything apparently correct except PM2.5.
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.
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
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.
Then you can try with;
lambda x: None if (x == 0xaaac or x == 0xaaab) else x/10
@vikitor87 Can you share the quirk file you've implemented ?
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=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=<DescriptorCapability.0: 0>, *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=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=<DescriptorCapability.0: 0>, *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: {
# <SimpleDescriptor endpoint=242 profile=41440 device_type=97
# input_clusters=[]
# output_clusters=[33]
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.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],
},
242: {
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
}
}
class TuyaAirQualityPM25(PM25, TuyaLocalCluster):
"""Tuya PM25 concentration measurement."""
# ug/m^3 https://github.com/Koenkk/zigbee2mqtt/issues/11033#issuecomment-1124056947
class TuyaCO2ManufCluster_2(TuyaNewManufCluster):
"""Tuya with Air quality device v2 with data points."""
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
2: DPToAttributeMapping(
TuyaAirQualityPM25.ep_attribute,
"measured_value",
lambda x: None if (x == 0xaaac or x == 0xaaab) else x * 0.1, #Modified "value" and divided by 10
#lambda x: x, #Test
),
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,
),
}
data_point_handlers = {
2: "_dp_2_attr_update",
18: "_dp_2_attr_update",
19: "_dp_2_attr_update",
20: "_dp_2_attr_update",
21: "_dp_2_attr_update",
22: "_dp_2_attr_update",
}
class TuyaCO2SensorGPP_2(CustomDevice):
"""Tuya Air quality device v2 with GPP."""
signature = {
# NodeDescriptor(logical_type=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=<DescriptorCapability.0: 0>, *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: {
# <SimpleDescriptor endpoint=242 profile=41440 device_type=97
# input_clusters=[]
# output_clusters=[33]
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
},
}
replacement = {
ENDPOINTS: {
1: {
DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaCO2ManufCluster_2,
TuyaAirQualityPM25,
TuyaAirQualityCO2,
TuyaAirQualityFormaldehyde,
TuyaAirQualityHumidity,
TuyaAirQualityTemperature,
TuyaAirQualityVOC,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
},
242: {
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
}
}
· Entities
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.
@vikitor87 thanks, it works (well at least it looks better now, i don't know the accuracy of the device)
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?