zha-device-handlers icon indicating copy to clipboard operation
zha-device-handlers copied to clipboard

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

Open julianjwong opened this issue 2 years ago • 21 comments

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.

julianjwong avatar May 14 '22 00:05 julianjwong

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,

javicalle avatar May 15 '22 12:05 javicalle

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

julianjwong avatar May 15 '22 14:05 julianjwong

Can you try replacing this expression?:

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

javicalle avatar May 15 '22 14:05 javicalle

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 avatar May 16 '22 02:05 julianjwong

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

julianjwong avatar May 16 '22 02:05 julianjwong

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.

javicalle avatar May 16 '22 07:05 javicalle

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 avatar May 16 '22 10:05 julianjwong

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

julianjwong avatar May 16 '22 10:05 julianjwong

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

javicalle avatar May 16 '22 12:05 javicalle

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

julianjwong avatar May 16 '22 13:05 julianjwong

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

afaucogney avatar May 16 '22 19:05 afaucogney

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?

julianjwong avatar May 16 '22 21:05 julianjwong

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

javicalle avatar May 17 '22 07:05 javicalle

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

julianjwong avatar May 17 '22 11:05 julianjwong

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.

Sesshoumaru-sama avatar May 17 '22 13:05 Sesshoumaru-sama

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.

javicalle avatar May 17 '22 19:05 javicalle

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?

julianjwong avatar May 17 '22 21:05 julianjwong

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

dmulcahey avatar May 17 '22 21:05 dmulcahey

#1406

I believe it's the same devices and issues

maxperron avatar May 19 '22 14:05 maxperron

#1707 is also related.

drmulligan avatar Oct 05 '22 16:10 drmulligan

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

julianjwong avatar Oct 05 '22 18:10 julianjwong

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.

vikitor87 avatar Dec 17 '22 18:12 vikitor87

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

javicalle avatar Dec 17 '22 19:12 javicalle

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.

vikitor87 avatar Dec 17 '22 20:12 vikitor87

Then you can try with;

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

javicalle avatar Dec 17 '22 20:12 javicalle

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

ylemoigne avatar Dec 18 '22 08:12 ylemoigne

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

cap1 cap2

vikitor87 avatar Dec 18 '22 13:12 vikitor87

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.

gudvinr avatar Dec 18 '22 21:12 gudvinr

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

ylemoigne avatar Dec 18 '22 21:12 ylemoigne

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?

lonnie776 avatar Dec 30 '22 08:12 lonnie776