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
685 stars 638 forks source link

[Device Support Request] Smart Gas & CO Alarm _TZE200_iuk8kupi #2474

Open zolakt opened 12 months ago

zolakt commented 12 months ago

Problem description

I've got one of these Tuya Gas and CO alarms from AliExpress

Zha finds the device, but without any entities.

I actually just need the CO level (note: monoxide, not CO2), don't care about gas or alarm that much.

Solution description

Is there an existing quirk that would at least make it detect the CO sensor?

Screenshots/Video

Screenshots/Video [Paste/upload your media here]

Device signature

Device signature ```json { "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": "0x0104", "device_type": "0x0051", "input_clusters": [ "0x0000", "0x0004", "0x0005", "0xef00" ], "output_clusters": [ "0x000a", "0x0019" ] }, "242": { "profile_id": "0xa1e0", "device_type": "0x0061", "input_clusters": [], "output_clusters": [ "0x0021" ] } }, "manufacturer": "_TZE200_iuk8kupi", "model": "TS0601", "class": "zigpy.device.Device" } ```

Diagnostic information

Diagnostic information ```json { "home_assistant": { "installation_type": "Home Assistant OS", "version": "2023.7.1", "dev": false, "hassio": true, "virtualenv": false, "python_version": "3.11.4", "docker": true, "arch": "aarch64", "timezone": "Europe/Belgrade", "os_name": "Linux", "os_version": "6.1.21-v8", "supervisor": "2023.07.1", "host_os": "Home Assistant OS 10.3", "docker_version": "23.0.6", "chassis": "embedded", "run_as_root": true }, "custom_components": { "hacs": { "version": "1.32.1", "requirements": [ "aiogithubapi>=22.10.1" ] }, "localtuya": { "version": "5.2.1", "requirements": [] }, "samsungtv_smart": { "version": "0.11.6", "requirements": [ "websocket-client!=1.4.0,>=0.58.0", "wakeonlan>=2.0.0", "aiofiles>=0.8.0", "casttube>=0.2.1" ] } }, "integration_manifest": { "domain": "zha", "name": "Zigbee Home Automation", "after_dependencies": [ "onboarding", "usb" ], "codeowners": [ "@dmulcahey", "@adminiuga", "@puddly" ], "config_flow": true, "dependencies": [ "file_upload" ], "documentation": "https://www.home-assistant.io/integrations/zha", "iot_class": "local_polling", "loggers": [ "aiosqlite", "bellows", "crccheck", "pure_pcapy3", "zhaquirks", "zigpy", "zigpy_deconz", "zigpy_xbee", "zigpy_zigate", "zigpy_znp" ], "requirements": [ "bellows==0.35.8", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.101", "zigpy-deconz==0.21.0", "zigpy==0.56.1", "zigpy-xbee==0.18.1", "zigpy-zigate==0.11.0", "zigpy-znp==0.11.2" ], "usb": [ { "vid": "10C4", "pid": "EA60", "description": "*2652*", "known_devices": [ "slae.sh cc2652rb stick" ] }, { "vid": "1A86", "pid": "55D4", "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus v2" ] }, { "vid": "10C4", "pid": "EA60", "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus" ] }, { "vid": "10C4", "pid": "EA60", "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ] }, { "vid": "1A86", "pid": "7523", "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ] }, { "vid": "1A86", "pid": "7523", "description": "*zigstar*", "known_devices": [ "ZigStar Coordinators" ] }, { "vid": "1CF1", "pid": "0030", "description": "*conbee*", "known_devices": [ "Conbee II" ] }, { "vid": "10C4", "pid": "8A2A", "description": "*zigbee*", "known_devices": [ "Nortek HUSBZB-1" ] }, { "vid": "0403", "pid": "6015", "description": "*zigate*", "known_devices": [ "ZiGate+" ] }, { "vid": "10C4", "pid": "EA60", "description": "*zigate*", "known_devices": [ "ZiGate" ] }, { "vid": "10C4", "pid": "8B34", "description": "*bv 2010/10*", "known_devices": [ "Bitron Video AV2010/10" ] } ], "zeroconf": [ { "type": "_esphomelib._tcp.local.", "name": "tube*" }, { "type": "_zigate-zigbee-gateway._tcp.local.", "name": "*zigate*" }, { "type": "_zigstar_gw._tcp.local.", "name": "*zigstar*" }, { "type": "_slzb-06._tcp.local.", "name": "slzb-06*" } ], "is_built_in": true }, "data": { "ieee": "**REDACTED**", "nwk": 40338, "manufacturer": "_TZE200_iuk8kupi", "model": "TS0601", "name": "_TZE200_iuk8kupi TS0601", "quirk_applied": false, "quirk_class": "zigpy.device.Device", "manufacturer_code": 4417, "power_source": "Mains", "lqi": 132, "rssi": -67, "last_seen": "2023-07-14T11:57:50", "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": "0x0104", "device_type": "0x0051", "input_clusters": [ "0x0000", "0x0004", "0x0005", "0xef00" ], "output_clusters": [ "0x000a", "0x0019" ] }, "242": { "profile_id": "0xa1e0", "device_type": "0x0061", "input_clusters": [], "output_clusters": [ "0x0021" ] } }, "manufacturer": "_TZE200_iuk8kupi", "model": "TS0601" }, "active_coordinator": false, "entities": [], "neighbors": [], "routes": [], "endpoint_names": [ { "name": "SMART_PLUG" }, { "name": "PROXY_BASIC" } ], "user_given_name": "CO Detector", "device_reg_id": "57dc6aeca0256c10521969c9906f4b1b", "area_id": "boiler_room", "cluster_details": { "1": { "device_type": { "name": "SMART_PLUG", "id": 81 }, "profile_id": 260, "in_clusters": { "0x0004": { "endpoint_attribute": "groups", "attributes": {}, "unsupported_attributes": {} }, "0x0005": { "endpoint_attribute": "scenes", "attributes": {}, "unsupported_attributes": {} }, "0xef00": { "endpoint_attribute": null, "attributes": {}, "unsupported_attributes": {} }, "0x0000": { "endpoint_attribute": "basic", "attributes": { "0x0001": { "attribute_name": "app_version", "value": 70 }, "0x0004": { "attribute_name": "manufacturer", "value": "_TZE200_iuk8kupi" }, "0x0005": { "attribute_name": "model", "value": "TS0601" } }, "unsupported_attributes": {} } }, "out_clusters": { "0x0019": { "endpoint_attribute": "ota", "attributes": {}, "unsupported_attributes": {} }, "0x000a": { "endpoint_attribute": "time", "attributes": {}, "unsupported_attributes": {} } } }, "242": { "device_type": { "name": "PROXY_BASIC", "id": 97 }, "profile_id": 41440, "in_clusters": {}, "out_clusters": { "0x0021": { "endpoint_attribute": "green_power", "attributes": {}, "unsupported_attributes": {} } } } } } } ```

Logs

Logs ```python [Paste the logs here] ```

Custom quirk

Custom quirk ```python [Paste your custom quirk here] ```

Additional information

If it is any help... I've added the device to Tuya just to so I can inspect it with their api explorer. This is how the DPs are mapped. There is a textual state and a measurement for each gas and CO sensor DP 1 - Gas Detection State - currently "Normal" DP 2 - Detected Gas - currently 0.00 DP 18 - CO State - currently "Normal" DP 19 - CO Value - currently 1.01

I just want to get these values as sensors. I don't need to control it, it can beep when it wants. I haven't found any calibration (like setting a threshold), in Tuya either.

zolakt commented 10 months ago

I've managed to create a quirk myself.

A few notes:

The values displayed should be the same as in the tuya app. Few minor notes:

Don't ask me how or why. This is the only thing that worked for me after a lot of trial and error. If anyone can improve this, it's more than welcome.

"""Tuya CO/Gas sensor."""
from typing import Dict

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import Basic, Ota, Time, Groups, Scenes
from zigpy.zcl.clusters.measurement import CarbonMonoxideConcentration, CarbonDioxideConcentration
from zigpy.zcl.clusters.security import IasZone

from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)
from zhaquirks.tuya import TuyaLocalCluster, TuyaManufCluster
from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaMCUCluster

ZONE_TYPE = 0x0001

class TuyaCarbonMonoxideConcentration(CarbonMonoxideConcentration, TuyaLocalCluster):
    """Tuya local CarbonMonoxideConcentration cluster."""

class TuyaCarbonMonoxideDetectorZone(IasZone, TuyaLocalCluster):
    """IAS Zone."""
    _CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Carbon_Monoxide_Sensor}

class TuyaMethaneConcentration(CarbonDioxideConcentration, TuyaLocalCluster):
    """Tuya local MethaneConcentration cluster."""

class TuyaMethaneDetectorZone(IasZone, TuyaLocalCluster):
    """IAS Zone."""   
    _CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Fire_Sensor}

class CoMethaneManufCluster(TuyaMCUCluster):

    attributes = TuyaMCUCluster.attributes.copy()

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        1: DPToAttributeMapping(
            TuyaMethaneDetectorZone.ep_attribute,
            "zone_status",
            lambda x: IasZone.ZoneStatus.Alarm_1 if not x else 0,
            endpoint_id=2
        ),
        2: DPToAttributeMapping(
            TuyaMethaneConcentration.ep_attribute,
            "measured_value",
            lambda x: x * 1e-5
        ),
        18: DPToAttributeMapping(
            TuyaCarbonMonoxideDetectorZone.ep_attribute,
            "zone_status",
            lambda x: IasZone.ZoneStatus.Alarm_1 if not x else 0
        ),
        19: DPToAttributeMapping(
            TuyaCarbonMonoxideConcentration.ep_attribute,
            "measured_value",
            lambda x: x * 1e-8
        )
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        18: "_dp_2_attr_update",
        19: "_dp_2_attr_update"
    }

class CoMethaneSensor(CustomDevice):
    """Tuya CO/Gas sensor."""

    signature = {
        MODELS_INFO: [("_TZE200_iuk8kupi", "TS0601")],
        ENDPOINTS: {
            # endpoints=1 profile=260 device_type=0x0051
            # in_clusters=[0x0000, 0x0004, 0x0005, 0xef00],
            # out_clusters=[0x000a, 0x0019]
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [
                    Time.cluster_id,
                    Ota.cluster_id,
                ],
            },
            242: {
                PROFILE_ID: 0xa1e0,
                DEVICE_TYPE: 0x0061,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [
                    0x0021
                ]
            }
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    CoMethaneManufCluster,
                    TuyaCarbonMonoxideConcentration,
                    TuyaCarbonMonoxideDetectorZone,
                    TuyaMethaneConcentration,
                ],
                OUTPUT_CLUSTERS: [
                    Time.cluster_id,
                    Ota.cluster_id,
                ],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
                INPUT_CLUSTERS: [
                    TuyaMethaneDetectorZone
                ],
                OUTPUT_CLUSTERS: [],
            },
            242: {
                DEVICE_TYPE: 0x0061,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [
                    0x0021
                ]
            }
        }
    }

image image

abertolin commented 9 months ago

Hi, I installed your custom quirk, now I can read some data from the sensor but what I see is a bit different. But the model il like yours. I'm confused

immagine

abertolin commented 9 months ago

And this is the model ... so CO and CH4

immagine

zolakt commented 9 months ago

I have no idea. Have you checked the device signature and stuff? Are the dps identical to mine?

The 3 entities you see, are ok. I had them the same. I changed the ids, names and icons through the UI later. But you are missing the 4th, for monoxide concentration,

Check the history of the senor you do have (the methane/dioxide one). Does it have wild oscillations, e.g. going from 0-1 to 10-30ppm, in a short time period? I'm asking because I had a similar issue with zones. That is why I separated one of them to another endpoint. In the same endpoint it was smashing them into the same sensor. When the state of either of dps would change, the entity would change twice, resulting in on/off flickering, since one zone is on, the other off. How and why... I don't know. I have no deep knowledge of this. I've just managed to patch something up after numerous attempts.

The only simple thing I can think of... try changing the entity ids of the ones you do have and restart. Maybe there is something with this... although I doubt it, since I'm using existing classes for Monoxide/Dioxide. I had a similar issue to yours when I tried to make a custom Methane class, but I couldn't get it to appear as a sensor. So I switched back to Dioxide.

abertolin commented 9 months ago

Hi, I rejoined the sensor and now I can see all the data. Thank you !!

The methane sensor has wild oscillations as you say, but I don't know if is normal.

immagine

zolakt commented 9 months ago

Great that it works for you.

I don't think it should have wild oscillations. At least mine doesn't. For me methane is usually 10, 20 or 30. Monoxide is usually 0 or 1.

By wild oscillations, I mean really fast change, in very quick succession. Basically, it looks like flickering if you watch it live. If you would see e.g. 30, then rapidly 1, then back to 30. Than would probably indicate that it's smashing the 2 sensors into the same entity.

I didn't have this issue, as I've mentioned. I've had it with the other 2 sensors (zone/alarm ones). It would flicker on and off very quickly, when I expected it to go (and stay) on. Also, if you see the values changing in both of your entities, I doubt you have this problem.

Anyway, maybe someone with more insights can help out. Would it be better to separate all 4 sensors, each in it's own endpoint? I think that would work, although I haven't tried it.