Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Please add support in ZHA for TZE200_ya4ft0w4 Tuya M100 Presence Sensor #3584

Open
Rabman416 opened this issue Dec 5, 2024 · 41 comments · May be fixed by #3612
Open

Please add support in ZHA for TZE200_ya4ft0w4 Tuya M100 Presence Sensor #3584

Rabman416 opened this issue Dec 5, 2024 · 41 comments · May be fixed by #3612

Comments

@Rabman416
Copy link

Problem description

The Tuya Presence Sensor is not supported in the ts0601_motion.py Quirk.

Solution description

Would like the presence sensor device to show the controls and sensors.

Screenshots/Video

Screenshots/Video

Screenshot 2024-12-05

Device signature

Device signature
{
  "node_descriptor": {
    "logical_type": 1,
    "complex_descriptor_available": 0,
    "user_descriptor_available": 0,
    "reserved": 0,
    "aps_flags": 0,
    "frequency_band": 8,
    "mac_capability_flags": 142,
    "manufacturer_code": 4660,
    "maximum_buffer_size": 108,
    "maximum_incoming_transfer_size": 0,
    "server_mask": 11264,
    "maximum_outgoing_transfer_size": 0,
    "descriptor_capability_field": 0
  },
  "endpoints": {
    "1": {
      "profile_id": "0x0104",
      "device_type": "0x0100",
      "input_clusters": [
        "0x0000",
        "0x0004",
        "0x0005",
        "0xef00"
      ],
      "output_clusters": []
    }
  },
  "manufacturer": "_TZE200_ya4ft0w4",
  "model": "TS0601",
  "class": "zigpy.device.Device"
}

Diagnostic information

Diagnostic information
{
  "home_assistant": {
    "installation_type": "Home Assistant OS",
    "version": "2024.12.0",
    "dev": false,
    "hassio": true,
    "virtualenv": false,
    "python_version": "3.13.0",
    "docker": true,
    "arch": "x86_64",
    "timezone": "America/Toronto",
    "os_name": "Linux",
    "os_version": "6.6.63-haos",
    "supervisor": "2024.11.4",
    "host_os": "Home Assistant OS 14.0",
    "docker_version": "27.2.0",
    "chassis": "vm",
    "run_as_root": true
  },
  "custom_components": {
    "toyota_na": {
      "documentation": "https://github.com/widewing/ha_toyota_na",
      "version": "2.4.10",
      "requirements": [
        "toyota-na==2.1.1"
      ]
    },
    "tplink_deco": {
      "documentation": "https://github.com/amosyuen/ha-tplink-deco",
      "version": "3.6.2",
      "requirements": [
        "pycryptodome>=3.12.0"
      ]
    },
    "meross_cloud": {
      "documentation": "https://www.home-assistant.io/components/meross_cloud",
      "version": "1.3.3",
      "requirements": [
        "meross_iot==0.4.7.3"
      ]
    },
    "spotcast": {
      "documentation": "https://github.com/fondberg/spotcast",
      "version": "v4.0.0",
      "requirements": [
        "spotipy==2.23.0"
      ]
    },
    "localtuya": {
      "documentation": "https://github.com/rospogrigio/localtuya/",
      "version": "5.2.1",
      "requirements": []
    },
    "webrtc": {
      "documentation": "https://github.com/AlexxIT/WebRTC",
      "version": "v3.6.0",
      "requirements": []
    },
    "tplink_router": {
      "documentation": "https://github.com/AlexandrErohin/home-assistant-tplink-router",
      "version": "v2.2.0",
      "requirements": [
        "tplinkrouterc6u==5.3.0"
      ]
    },
    "meross_lan": {
      "documentation": "https://github.com/krahabb/meross_lan",
      "version": "5.4.0",
      "requirements": []
    },
    "hacs": {
      "documentation": "https://hacs.xyz/docs/configuration/start",
      "version": "2.0.1",
      "requirements": [
        "aiogithubapi>=22.10.1"
      ]
    },
    "emporia_vue": {
      "documentation": "https://github.com/magico13/ha-emporia-vue",
      "version": "0.10.0",
      "requirements": [
        "pyemvue==0.18.6"
      ]
    },
    "eufy_security": {
      "documentation": "https://github.com/fuatakgun/eufy_security",
      "version": "8.1.1",
      "requirements": [
        "websocket-client==1.8.0",
        "aiortsp==1.4.0"
      ]
    },
    "sonoff": {
      "documentation": "https://github.com/AlexxIT/SonoffLAN",
      "version": "3.8.1",
      "requirements": [
        "pycryptodome>=3.6.6"
      ]
    },
    "reolink_dev": {
      "documentation": "https://github.com/fwestenberg/reolink_dev",
      "version": "0.61",
      "requirements": [
        "reolink==0.0.64",
        "aiosmtpd>=1.4.2"
      ]
    },
    "watchman": {
      "documentation": "https://github.com/dummylabs/thewatchman",
      "version": "0.6.5",
      "requirements": [
        "prettytable==3.12.0"
      ]
    }
  },
  "integration_manifest": {
    "domain": "zha",
    "name": "Zigbee Home Automation",
    "after_dependencies": [
      "hassio",
      "onboarding",
      "usb"
    ],
    "codeowners": [
      "dmulcahey",
      "adminiuga",
      "puddly",
      "TheJulianJES"
    ],
    "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",
      "zha",
      "universal_silabs_flasher"
    ],
    "requirements": [
      "universal-silabs-flasher==0.0.25",
      "zha==0.0.41"
    ],
    "usb": [
      {
        "vid": "10C4",
        "pid": "EA60",
        "description": "*2652*",
        "known_devices": [
          "slae.sh cc2652rb stick"
        ]
      },
      {
        "vid": "10C4",
        "pid": "EA60",
        "description": "*slzb-07*",
        "known_devices": [
          "smlight slzb-07"
        ]
      },
      {
        "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": "0403",
        "pid": "6015",
        "description": "*conbee*",
        "known_devices": [
          "Conbee III"
        ]
      },
      {
        "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": "_uzg-01._tcp.local.",
        "name": "uzg-01*"
      },
      {
        "type": "_slzb-06._tcp.local.",
        "name": "slzb-06*"
      },
      {
        "type": "_xzg._tcp.local.",
        "name": "xzg*"
      },
      {
        "type": "_czc._tcp.local.",
        "name": "czc*"
      },
      {
        "type": "_zigbee-coordinator._tcp.local.",
        "name": "*"
      }
    ],
    "is_built_in": true,
    "overwrites_built_in": false
  },
  "setup_times": {
    "null": {
      "setup": 0.00011468700540717691
    },
    "2d2dff476a1cdc2423001b831d5795ea": {
      "wait_import_platforms": -0.048387823000666685,
      "config_entry_setup": 49.118052380988956
    }
  },
  "data": {
    "ieee": "**REDACTED**",
    "nwk": 61965,
    "manufacturer": "_TZE200_ya4ft0w4",
    "model": "TS0601",
    "name": "_TZE200_ya4ft0w4 TS0601",
    "quirk_applied": false,
    "quirk_class": "zigpy.device.Device",
    "quirk_id": null,
    "manufacturer_code": 4660,
    "power_source": "Mains",
    "lqi": 80,
    "rssi": null,
    "last_seen": "2024-12-05T16:01:24",
    "available": true,
    "device_type": "Router",
    "signature": {
      "node_descriptor": {
        "logical_type": 1,
        "complex_descriptor_available": 0,
        "user_descriptor_available": 0,
        "reserved": 0,
        "aps_flags": 0,
        "frequency_band": 8,
        "mac_capability_flags": 142,
        "manufacturer_code": 4660,
        "maximum_buffer_size": 108,
        "maximum_incoming_transfer_size": 0,
        "server_mask": 11264,
        "maximum_outgoing_transfer_size": 0,
        "descriptor_capability_field": 0
      },
      "endpoints": {
        "1": {
          "profile_id": "0x0104",
          "device_type": "0x0100",
          "input_clusters": [
            "0x0000",
            "0x0004",
            "0x0005",
            "0xef00"
          ],
          "output_clusters": []
        }
      },
      "manufacturer": "_TZE200_ya4ft0w4",
      "model": "TS0601"
    },
    "active_coordinator": false,
    "entities": [],
    "neighbors": [],
    "routes": [],
    "endpoint_names": [
      {
        "name": "ON_OFF_LIGHT"
      }
    ],
    "user_given_name": "Loginovo Kitchen TZE200_ya4ft0w4 TS0601",
    "device_reg_id": "f52ebd470494b7ccd5592d2d4ec5b953",
    "area_id": "kitchen",
    "cluster_details": {
      "1": {
        "device_type": {
          "name": "ON_OFF_LIGHT",
          "id": 256
        },
        "profile_id": 260,
        "in_clusters": {
          "0x0000": {
            "endpoint_attribute": "basic",
            "attributes": {
              "0x0013": {
                "attribute": "ZCLAttributeDef(id=0x0013, name='alarm_mask', type=<flag 'AlarmMask'>, zcl_type=<DataTypeId.map8: 24>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0001": {
                "attribute": "ZCLAttributeDef(id=0x0001, name='app_version', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0xfffd": {
                "attribute": "ZCLAttributeDef(id=0xFFFD, name='cluster_revision', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0006": {
                "attribute": "ZCLAttributeDef(id=0x0006, name='date_code', type=<class 'zigpy.types.basic.LimitedCharString.<locals>.LimitedCharString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0012": {
                "attribute": "ZCLAttributeDef(id=0x0012, name='device_enabled', type=<enum 'Bool'>, zcl_type=<DataTypeId.bool_: 16>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0014": {
                "attribute": "ZCLAttributeDef(id=0x0014, name='disable_local_config', type=<flag 'DisableLocalConfig'>, zcl_type=<DataTypeId.map8: 24>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0008": {
                "attribute": "ZCLAttributeDef(id=0x0008, name='generic_device_class', type=<enum 'GenericDeviceClass'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0009": {
                "attribute": "ZCLAttributeDef(id=0x0009, name='generic_device_type', type=<enum 'GenericLightingDeviceType'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0003": {
                "attribute": "ZCLAttributeDef(id=0x0003, name='hw_version', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0010": {
                "attribute": "ZCLAttributeDef(id=0x0010, name='location_desc', type=<class 'zigpy.types.basic.LimitedCharString.<locals>.LimitedCharString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0004": {
                "attribute": "ZCLAttributeDef(id=0x0004, name='manufacturer', type=<class 'zigpy.types.basic.LimitedCharString.<locals>.LimitedCharString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": "_TZE200_ya4ft0w4"
              },
              "0x000c": {
                "attribute": "ZCLAttributeDef(id=0x000C, name='manufacturer_version_details', type=<class 'zigpy.types.basic.CharacterString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0005": {
                "attribute": "ZCLAttributeDef(id=0x0005, name='model', type=<class 'zigpy.types.basic.LimitedCharString.<locals>.LimitedCharString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": "TS0601"
              },
              "0x0011": {
                "attribute": "ZCLAttributeDef(id=0x0011, name='physical_env', type=<enum 'PhysicalEnvironment'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0007": {
                "attribute": "ZCLAttributeDef(id=0x0007, name='power_source', type=<enum 'PowerSource'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x000a": {
                "attribute": "ZCLAttributeDef(id=0x000A, name='product_code', type=<class 'zigpy.types.basic.LVBytes'>, zcl_type=<DataTypeId.octstr: 65>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x000e": {
                "attribute": "ZCLAttributeDef(id=0x000E, name='product_label', type=<class 'zigpy.types.basic.CharacterString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x000b": {
                "attribute": "ZCLAttributeDef(id=0x000B, name='product_url', type=<class 'zigpy.types.basic.CharacterString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0xfffe": {
                "attribute": "ZCLAttributeDef(id=0xFFFE, name='reporting_status', type=<enum 'AttributeReportingStatus'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x000d": {
                "attribute": "ZCLAttributeDef(id=0x000D, name='serial_number', type=<class 'zigpy.types.basic.CharacterString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0002": {
                "attribute": "ZCLAttributeDef(id=0x0002, name='stack_version', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x4000": {
                "attribute": "ZCLAttributeDef(id=0x4000, name='sw_build_id', type=<class 'zigpy.types.basic.CharacterString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0000": {
                "attribute": "ZCLAttributeDef(id=0x0000, name='zcl_version', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              }
            },
            "unsupported_attributes": []
          },
          "0x0004": {
            "endpoint_attribute": "groups",
            "attributes": {
              "0xfffd": {
                "attribute": "ZCLAttributeDef(id=0xFFFD, name='cluster_revision', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0000": {
                "attribute": "ZCLAttributeDef(id=0x0000, name='name_support', type=<flag 'NameSupport'>, zcl_type=<DataTypeId.map8: 24>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0xfffe": {
                "attribute": "ZCLAttributeDef(id=0xFFFE, name='reporting_status', type=<enum 'AttributeReportingStatus'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              }
            },
            "unsupported_attributes": []
          },
          "0x0005": {
            "endpoint_attribute": "scenes",
            "attributes": {
              "0xfffd": {
                "attribute": "ZCLAttributeDef(id=0xFFFD, name='cluster_revision', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0000": {
                "attribute": "ZCLAttributeDef(id=0x0000, name='count', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0002": {
                "attribute": "ZCLAttributeDef(id=0x0002, name='current_group', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0001": {
                "attribute": "ZCLAttributeDef(id=0x0001, name='current_scene', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0005": {
                "attribute": "ZCLAttributeDef(id=0x0005, name='last_configured_by', type=<class 'zigpy.types.named.EUI64'>, zcl_type=<DataTypeId.EUI64: 240>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0004": {
                "attribute": "ZCLAttributeDef(id=0x0004, name='name_support', type=<flag 'NameSupport'>, zcl_type=<DataTypeId.map8: 24>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0xfffe": {
                "attribute": "ZCLAttributeDef(id=0xFFFE, name='reporting_status', type=<enum 'AttributeReportingStatus'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0003": {
                "attribute": "ZCLAttributeDef(id=0x0003, name='scene_valid', type=<enum 'Bool'>, zcl_type=<DataTypeId.bool_: 16>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              }
            },
            "unsupported_attributes": []
          },
          "0xef00": {
            "endpoint_attribute": null,
            "attributes": {},
            "unsupported_attributes": []
          }
        },
        "out_clusters": {}
      }
    }
  }
}

Logs

Logs
[Paste the logs here]

Custom quirk

Custom quirk
[Paste your custom quirk here]

Additional information

I tried entering the device name in the different various sections of the current Quirk with no success. It only shows two Diagnostic entities. Does not show the controls or sensor information.

This is the link to the device 24G Presence sensor on AliExpress. I did order from this vendor before and the device name on that order came as _TZE204_7gclukjs and it works.

https://www.aliexpress.com/item/1005005249827156.html?spm=a2g0o.order_list.order_list_main.5.54441802Kg4T6C

@pjunzip
Copy link

pjunzip commented Dec 9, 2024

I also got this model, TZE200_ya4ft0w4, which seems to be a new version. There are no quirks available for it yet. Could you please add support for this device?

With this model, in the Smart Life app, the human detection status is displayed as three states: cleared, moving, and exits. However, when connected to ZHA, no entities are detected at all. If I connect it through the Tuya integration via the Tuya Zigbee hub in Home Assistant, it only displays two states: cleared and occupancy.

The issue is that when the status is moving in the Smart Life app, it is interpreted as cleared in Home Assistant, which makes it unusable.

I would prefer ZHA to support this device rather than relying on the Tuya integration. Thank you!

@Itay1787
Copy link

Hi, I have the same model. Does he have no quirk at all?
Is it impossible to take from the other models that are similar to it?
I tried to take this code - https://gist.githubusercontent.com/fixtse/09316d48f23688938fce31add711ea07/raw/358353f9274fe0654cd784f4301cab011ddda942/TS0601_radar.py from here -https://fixtse.com/blog/zy-m100-full-zha-support
and added his model in the model sections, but it didn't work.
Do I need to add the model in another place for it to work?

@Rabman416
Copy link
Author

Rabman416 commented Dec 11, 2024

Hi, I have the same model. Does he have no quirk at all? Is it impossible to take from the other models that are similar to it? I tried to take this code - https://gist.githubusercontent.com/fixtse/09316d48f23688938fce31add711ea07/raw/358353f9274fe0654cd784f4301cab011ddda942/TS0601_radar.py from here -https://fixtse.com/blog/zy-m100-full-zha-support and added his model in the model sections, but it didn't work. Do I need to add the model in another place for it to work?

I tried putting the model in various areas with no success. I think it involves more than that.

@prairiesnpr
Copy link
Contributor

prairiesnpr commented Dec 11, 2024

This should get you started, the rest of the DPs can be found in the z2m source

This is mapping the presence and move states to an occupied state.

from zhaquirks.tuya.builder import TuyaQuirkBuilder
from zhaquirks.tuya import TuyaLocalCluster
from zigpy.zcl.clusters.measurement import OccupancySensing

class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
    """Tuya local OccupancySensing cluster."""

(
    TuyaQuirkBuilder("_TZE200_ya4ft0w4", "TS0601")
    .tuya_dp(
        dp_id=1,
        ep_attribute=TuyaOccupancySensing.ep_attribute,
        attribute_name=OccupancySensing.AttributeDefs.occupancy.name,
        converter=lambda x: True if x in (1, 2) else False,
    )
    .adds(TuyaOccupancySensing) 
    .skip_configuration()
    .add_to_registry()
)

@Rabman416
Copy link
Author

This should get you started, the rest of the DPs can be found in the z2m source

This is mapping the presence and move states to an occupied state.

from zhaquirks.tuya.builder import TuyaQuirkBuilder
from zhaquirks.tuya import TuyaLocalCluster
from zigpy.zcl.clusters.measurement import OccupancySensing

class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
    """Tuya local OccupancySensing cluster."""

(
    TuyaQuirkBuilder("_TZE200_ya4ft0w4", "TS0601")
    .tuya_dp(
        dp_id=1,
        ep_attribute=TuyaOccupancySensing.ep_attribute,
        attribute_name=OccupancySensing.AttributeDefs.occupancy.name,
        converter=lambda x: True if x in (1, 2) else False,
    )
    .skip_configuration()
    .add_to_registry()
)

Thank you. Not sure how I would implement this. Would I add it to the existing quirk?

@prairiesnpr
Copy link
Contributor

Thank you. Not sure how I would implement this. Would I add it to the existing quirk?

Assuming you have enabled custom quirks, if not the ZHA docs detail how to enable them, then just save it to a file in the custom quirk directory. It's the newer v2 format, see the following links for more details on extending it.

https://github.com/zigpy/zha-device-handlers/blob/dev/tuya.md
https://github.com/zigpy/zha-device-handlers/wiki/Tuya-%E2%80%90-v2-Quirk-with-TuyaQuirkBuilder

If you get a MultipleQuirksMatchException error, see zigpy/zigpy#1508

@Rabman416
Copy link
Author

I will give it a try.

@Rabman416
Copy link
Author

Rabman416 commented Dec 12, 2024

Well I looked into things this morning and realized I am out of my depth. I don't know how I integrate this new code with my existing quirk which I have included below. I do have other presence sensors and don't want to mess them up.

CODE:

"""BlitzWolf IS-3/Tuya motion rechargeable occupancy sensor."""

import math
from typing import Dict, Optional, Tuple, Union

from zigpy.profiles import zgp, zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.general import (
AnalogInput,
AnalogOutput,
Basic,
GreenPowerProxy,
Groups,
Identify,
Ota,
Scenes,
Time,
)
from zigpy.zcl.clusters.measurement import (
IlluminanceMeasurement,
OccupancySensing,
RelativeHumidity,
TemperatureMeasurement,
)
from zigpy.zcl.clusters.security import IasZone

from zhaquirks import Bus, LocalDataCluster, MotionOnEvent
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
MOTION_EVENT,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.tuya import TuyaLocalCluster, TuyaManufCluster, TuyaNewManufCluster
from zhaquirks.tuya.mcu import (
DPToAttributeMapping,
TuyaAttributesCluster,
TuyaMCUCluster,
)

ZONE_TYPE = 0x0001

class TuyaMmwRadarSelfTest(t.enum8):
"""Mmw radar self test values."""

TESTING = 0
TEST_SUCCESS = 1
TEST_FAILURE = 2
OTHER = 3
COMM_FAULT = 4
RADAR_FAULT = 5

class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
"""Tuya local OccupancySensing cluster."""

class TuyaAnalogInput(AnalogInput, TuyaLocalCluster):
"""Tuya local AnalogInput cluster."""

class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster):
"""Tuya local IlluminanceMeasurement cluster."""

class TuyaMmwRadarSensitivity(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for sensitivity."""

def __init__(self, *args, **kwargs):
    """Init."""
    super().__init__(*args, **kwargs)
    self._update_attribute(self.attributes_by_name["description"].id, "Sensitivity")
    self._update_attribute(self.attributes_by_name["min_present_value"].id, 1)
    self._update_attribute(self.attributes_by_name["max_present_value"].id, 9)
    self._update_attribute(self.attributes_by_name["resolution"].id, 1)

class TuyaMmwRadarMinRange(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for min range."""

def __init__(self, *args, **kwargs):
    """Init."""
    super().__init__(*args, **kwargs)
    self._update_attribute(self.attributes_by_name["description"].id, "Min range")
    self._update_attribute(self.attributes_by_name["min_present_value"].id, 0)
    self._update_attribute(self.attributes_by_name["max_present_value"].id, 950)
    self._update_attribute(self.attributes_by_name["resolution"].id, 10)
    self._update_attribute(
        self.attributes_by_name["engineering_units"].id, 118
    )  # 31: meters

class TuyaMmwRadarMaxRange(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for max range."""

def __init__(self, *args, **kwargs):
    """Init."""
    super().__init__(*args, **kwargs)
    self._update_attribute(self.attributes_by_name["description"].id, "Max range")
    self._update_attribute(self.attributes_by_name["min_present_value"].id, 10)
    self._update_attribute(self.attributes_by_name["max_present_value"].id, 950)
    self._update_attribute(self.attributes_by_name["resolution"].id, 10)
    self._update_attribute(
        self.attributes_by_name["engineering_units"].id, 118
    )  # 31: meters

class TuyaMmwRadarDetectionDelay(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for detection delay."""

def __init__(self, *args, **kwargs):
    """Init."""
    super().__init__(*args, **kwargs)
    self._update_attribute(
        self.attributes_by_name["description"].id, "Detection delay"
    )
    self._update_attribute(self.attributes_by_name["min_present_value"].id, 000)
    self._update_attribute(self.attributes_by_name["max_present_value"].id, 20000)
    self._update_attribute(self.attributes_by_name["resolution"].id, 100)
    self._update_attribute(
        self.attributes_by_name["engineering_units"].id, 159
    )  # 73: seconds

class TuyaMmwRadarFadingTime(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for fading time."""

def __init__(self, *args, **kwargs):
    """Init."""
    super().__init__(*args, **kwargs)
    self._update_attribute(self.attributes_by_name["description"].id, "Fading time")
    self._update_attribute(self.attributes_by_name["min_present_value"].id, 2000)
    self._update_attribute(self.attributes_by_name["max_present_value"].id, 200000)
    self._update_attribute(self.attributes_by_name["resolution"].id, 1000)
    self._update_attribute(
        self.attributes_by_name["engineering_units"].id, 159
    )  # 73: seconds

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

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

class NeoBatteryLevel(t.enum8):
"""NEO battery level enum."""

BATTERY_FULL = 0x00
BATTERY_HIGH = 0x01
BATTERY_MEDIUM = 0x02
BATTERY_LOW = 0x03
USB_POWER = 0x04

class NeoMotionManufCluster(TuyaNewManufCluster):
"""Neo manufacturer cluster."""

attributes = TuyaNewManufCluster.attributes.copy()
attributes.update(
    {
        0xEF0D: ("dp_113", t.enum8, True),  # ramdom attribute ID
    }
)

dp_to_attribute: Dict[int, DPToAttributeMapping] = {
    101: DPToAttributeMapping(
        TuyaOccupancySensing.ep_attribute,
        "occupancy",
    ),
    104: DPToAttributeMapping(
        TuyaTemperatureMeasurement.ep_attribute,
        "measured_value",
        lambda x: x * 10,
    ),
    105: DPToAttributeMapping(
        TuyaRelativeHumidity.ep_attribute,
        "measured_value",
        lambda x: x * 100,
    ),
    113: DPToAttributeMapping(
        TuyaNewManufCluster.ep_attribute,
        "dp_113",
    ),
}

data_point_handlers = {
    101: "_dp_2_attr_update",
    104: "_dp_2_attr_update",
    105: "_dp_2_attr_update",
    113: "_dp_2_attr_update",
}

class TuyaMmwRadarTargetDistance(TuyaAttributesCluster, AnalogInput):
"""AnalogInput cluster for target distance."""

def __init__(self, *args, **kwargs):
    """Init."""
    super().__init__(*args, **kwargs)
    self._update_attribute(
        self.attributes_by_name["description"].id, "Target distance"
    )
    self._update_attribute(
        self.attributes_by_name["engineering_units"].id, 31
    )  # 31: meters

class MmwRadarManufCluster(TuyaMCUCluster):
"""Neo manufacturer cluster."""

# # Possible DPs and values
# presence_state: presence
# target distance: 1.61m
# illuminance: 250lux
# sensitivity: 9
# minimum_detection_distance: 0.00m
# maximum_detection_distance: 4.05m
# dp_detection_delay: 0.1
# dp_fading_time: 5.0
# ¿illuminance?: 255lux
# presence_brightness: no control
# no_one_brightness: no control
# current_brightness: off

attributes = TuyaMCUCluster.attributes.copy()
attributes.update(
    {
        # ramdom attribute IDs
        0xEF01: ("occupancy", t.uint32_t, True),
        0xEF02: ("sensitivity", t.uint32_t, True),
        0xEF03: ("min_range", t.uint32_t, True),
        0xEF04: ("max_range", t.uint32_t, True),
        0xEF06: ("self_test", TuyaMmwRadarSelfTest, True),
        0xEF09: ("target_distance", t.uint32_t, True),
        0xEF65: ("detection_delay", t.uint32_t, True),
        0xEF66: ("fading_time", t.uint32_t, True),
        0xEF67: ("cli", t.CharacterString, True),
        0xEF68: ("illuminance", t.uint32_t, True),
        0xEF69: ("dp_105", t.enum8, True),
        0xEF6A: ("dp_106", t.enum8, True),
        0xEF6B: ("dp_107", t.enum8, True),
        0xEF6C: ("dp_108", t.uint32_t, True),
    }
)

dp_to_attribute: Dict[int, DPToAttributeMapping] = {
    1: DPToAttributeMapping(
        TuyaOccupancySensing.ep_attribute,
        "occupancy",
    ),
    2: DPToAttributeMapping(
        TuyaMmwRadarSensitivity.ep_attribute,
        "present_value",
    ),
    3: DPToAttributeMapping(
        TuyaMmwRadarMinRange.ep_attribute,
        "present_value",
        endpoint_id=2,
    ),
    4: DPToAttributeMapping(
        TuyaMmwRadarMaxRange.ep_attribute,
        "present_value",
        endpoint_id=3,
    ),
    6: DPToAttributeMapping(
        TuyaMCUCluster.ep_attribute,
        "self_test",
    ),
    9: DPToAttributeMapping(
        TuyaMmwRadarTargetDistance.ep_attribute,
        "present_value",
        lambda x: x / 100,
    ),
    101: DPToAttributeMapping(
        TuyaMmwRadarDetectionDelay.ep_attribute,
        "present_value",
        converter=lambda x: x * 100,
        dp_converter=lambda x: x // 100,
        endpoint_id=4,
    ),
    102: DPToAttributeMapping(
        TuyaMmwRadarFadingTime.ep_attribute,
        "present_value",
        converter=lambda x: x * 100,
        dp_converter=lambda x: x // 100,
        endpoint_id=5,
    ),
    103: DPToAttributeMapping(
        TuyaMCUCluster.ep_attribute,
        "cli",
    ),
    104: DPToAttributeMapping(
        TuyaIlluminanceMeasurement.ep_attribute,
        "measured_value",
        converter=lambda x: int(math.log10(x) * 10000 + 1) if x > 0 else int(0),
    ),
    105: DPToAttributeMapping(
        TuyaOccupancySensing.ep_attribute,
        "occupancy",
    ),
    106: DPToAttributeMapping(
        TuyaMmwRadarSensitivity.ep_attribute,
        "present_value",
    ),
    107: DPToAttributeMapping(
        TuyaMmwRadarMaxRange.ep_attribute,
        "present_value",
        endpoint_id=3,
    ),
    108: DPToAttributeMapping(
        TuyaMmwRadarMinRange.ep_attribute,
        "present_value",
        endpoint_id=2,
    ),
    109: DPToAttributeMapping(
        TuyaMmwRadarTargetDistance.ep_attribute,
        "present_value",
    ),
    110: DPToAttributeMapping(
        TuyaMmwRadarFadingTime.ep_attribute,
        "present_value",
        converter=lambda x: x * 100,
        dp_converter=lambda x: x // 100,
        endpoint_id=5,
    ),
    111: DPToAttributeMapping(
        TuyaMmwRadarDetectionDelay.ep_attribute,
        "present_value",
        converter=lambda x: x * 100,
        dp_converter=lambda x: x // 100,
        endpoint_id=4,
    ),
}

data_point_handlers = {
    1: "_dp_2_attr_update",
    2: "_dp_2_attr_update",
    3: "_dp_2_attr_update",
    4: "_dp_2_attr_update",
    6: "_dp_2_attr_update",
    9: "_dp_2_attr_update",
    101: "_dp_2_attr_update",
    102: "_dp_2_attr_update",
    103: "_dp_2_attr_update",
    104: "_dp_2_attr_update",
    105: "_dp_2_attr_update",
    106: "_dp_2_attr_update",
    107: "_dp_2_attr_update",
    108: "_dp_2_attr_update",
    109: "_dp_2_attr_update",
    110: "_dp_2_attr_update",
    111: "_dp_2_attr_update",
}

class MotionCluster(LocalDataCluster, MotionOnEvent):
"""Tuya Motion Sensor."""

_CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Motion_Sensor}
reset_s = 15

class TuyaManufacturerClusterMotion(TuyaManufCluster):
"""Manufacturer Specific Cluster of the Motion device."""

def handle_cluster_request(
    self,
    hdr: foundation.ZCLHeader,
    args: Tuple[TuyaManufCluster.Command],
    *,
    dst_addressing: Optional[
        Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK]
    ] = None,
) -> None:
    """Handle cluster request."""
    tuya_cmd = args[0]
    self.debug("handle_cluster_request--> hdr: %s, args: %s", hdr, args)
    if hdr.command_id == 0x0001 and tuya_cmd.command_id == 1027:
        self.endpoint.device.motion_bus.listener_event(MOTION_EVENT)

class TuyaMotion(CustomDevice):
"""BW-IS3 occupancy sensor."""

def __init__(self, *args, **kwargs):
    """Init device."""
    self.motion_bus = Bus()
    super().__init__(*args, **kwargs)

signature = {
    #  endpoint=1 profile=260 device_type=0 device_version=0 input_clusters=[0, 3]
    #  output_clusters=[3, 25]>
    MODELS_INFO: [("_TYST11_i5j6ifxj", "5j6ifxj"), ("_TYST11_7hfcudw5", "hfcudw5")],
    ENDPOINTS: {
        1: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
            INPUT_CLUSTERS: [Basic.cluster_id, Identify.cluster_id],
            OUTPUT_CLUSTERS: [Identify.cluster_id, Ota.cluster_id],
        }
    },
}

replacement = {
    ENDPOINTS: {
        1: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
            INPUT_CLUSTERS: [
                Basic.cluster_id,
                Identify.cluster_id,
                MotionCluster,
                TuyaManufacturerClusterMotion,
            ],
            OUTPUT_CLUSTERS: [Identify.cluster_id, Ota.cluster_id],
        }
    }
}

class NeoMotion(CustomDevice):
"""NAS-PD07 occupancy sensor."""

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

replacement = {
    ENDPOINTS: {
        1: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
            INPUT_CLUSTERS: [
                Basic.cluster_id,
                Groups.cluster_id,
                Scenes.cluster_id,
                NeoMotionManufCluster,
                TuyaOccupancySensing,
                TuyaTemperatureMeasurement,
                TuyaRelativeHumidity,
            ],
            OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
        }
    }
}

class MmwRadarMotion(CustomDevice):
"""Millimeter wave occupancy sensor."""

signature = {
    #  endpoint=1, profile=260, device_type=81, device_version=1,
    #  input_clusters=[0, 4, 5, 61184], output_clusters=[25, 10]
    MODELS_INFO: [
        ("_TZE200_ar0slwnd", "TS0601"),
        ("_TZE200_sfiy5tfs", "TS0601"),
        ("_TZE200_mrf6vtua", "TS0601"),
        ("_TZE200_ztc6ggyl", "TS0601"),
        ("_TZE204_ztc6ggyl", "TS0601"),
        ("_TZE200_wukb7rhc", "TS0601"),
        ("_TZE204_7gclukjs", "TS0601"),# Added this record for Old Loginovos
    ],
    ENDPOINTS: {
        1: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
            INPUT_CLUSTERS: [
                Basic.cluster_id,
                Groups.cluster_id,
                Scenes.cluster_id,
                TuyaNewManufCluster.cluster_id,
            ],
            OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
        },
    },
}

replacement = {
    ENDPOINTS: {
        1: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
            INPUT_CLUSTERS: [
                Basic.cluster_id,
                Groups.cluster_id,
                Scenes.cluster_id,
                MmwRadarManufCluster,
                TuyaOccupancySensing,
                TuyaAnalogInput,
                TuyaIlluminanceMeasurement,
                TuyaMmwRadarTargetDistance,
                TuyaMmwRadarSensitivity,
            ],
            OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
        },
        2: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
            INPUT_CLUSTERS: [
                TuyaMmwRadarMinRange,
            ],
            OUTPUT_CLUSTERS: [],
        },
        3: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
            INPUT_CLUSTERS: [
                TuyaMmwRadarMaxRange,
            ],
            OUTPUT_CLUSTERS: [],
        },
        4: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
            INPUT_CLUSTERS: [
                TuyaMmwRadarDetectionDelay,
            ],
            OUTPUT_CLUSTERS: [],
        },
        5: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
            INPUT_CLUSTERS: [
                TuyaMmwRadarFadingTime,
            ],
            OUTPUT_CLUSTERS: [],
        },
    }
}

class MmwRadarMotionGPP(CustomDevice):
"""Millimeter wave occupancy sensor."""

signature = {
    #  endpoint=1, profile=260, device_type=81, device_version=1,
    #  input_clusters=[4, 5, 61184, 0], output_clusters=[25, 10])
    MODELS_INFO: [
        ("_TZE200_ar0slwnd", "TS0601"),
        ("_TZE200_sfiy5tfs", "TS0601"),
        ("_TZE200_mrf6vtua", "TS0601"),
        ("_TZE204_sxm7l9xa", "TS0601"),
        ("_TZE200_ya4ft0w4", "TS0601"), #Added this record for New Loginovos
    ],
    ENDPOINTS: {
        1: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
            INPUT_CLUSTERS: [
                Basic.cluster_id,
                Groups.cluster_id,
                Scenes.cluster_id,
                TuyaNewManufCluster.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: zgp.PROFILE_ID,
            DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
            INPUT_CLUSTERS: [],
            OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
        },
    },
}

replacement = {
    ENDPOINTS: {
        1: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
            INPUT_CLUSTERS: [
                Basic.cluster_id,
                Groups.cluster_id,
                Scenes.cluster_id,
                MmwRadarManufCluster,
                TuyaOccupancySensing,
                TuyaAnalogInput,
                TuyaIlluminanceMeasurement,
                TuyaMmwRadarTargetDistance,
                TuyaMmwRadarSensitivity,
            ],
            OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
        },
        2: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
            INPUT_CLUSTERS: [
                TuyaMmwRadarMinRange,
            ],
            OUTPUT_CLUSTERS: [],
        },
        3: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
            INPUT_CLUSTERS: [
                TuyaMmwRadarMaxRange,
            ],
            OUTPUT_CLUSTERS: [],
        },
        4: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
            INPUT_CLUSTERS: [
                TuyaMmwRadarDetectionDelay,
            ],
            OUTPUT_CLUSTERS: [],
        },
        5: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
            INPUT_CLUSTERS: [
                TuyaMmwRadarFadingTime,
            ],
            OUTPUT_CLUSTERS: [],
        },
        242: {
            PROFILE_ID: zgp.PROFILE_ID,
            DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
            INPUT_CLUSTERS: [],
            OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
        },
    }
}

@prairiesnpr
Copy link
Contributor

prairiesnpr commented Dec 12, 2024

Just place the following in a file named tuya_TZE200_ya4ft0w4.py and then place that file in your custom quirks directory, usually something like config/custom_zha_quirks, the directory would be configured in configuration.yaml.

from zhaquirks.tuya.builder import TuyaQuirkBuilder
from zhaquirks.tuya import TuyaLocalCluster
from zigpy.zcl.clusters.measurement import OccupancySensing

class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
    """Tuya local OccupancySensing cluster."""

(
    TuyaQuirkBuilder("_TZE200_ya4ft0w4", "TS0601")
    .tuya_dp(
        dp_id=1,
        ep_attribute=TuyaOccupancySensing.ep_attribute,
        attribute_name=OccupancySensing.AttributeDefs.occupancy.name,
        converter=lambda x: True if x in (1, 2) else False,
    )
    .adds(TuyaOccupancySensing) 
    .skip_configuration()
    .add_to_registry()
)

@Rabman416
Copy link
Author

Awesome. I will try that.

@Rabman416
Copy link
Author

Thanks for the additional instructions. I now have Occupancy showing up in HA for the sensor which is the most important. I guess I will wait until other controls and sensors are added. I am most appreciative!

@prairiesnpr
Copy link
Contributor

prairiesnpr commented Dec 12, 2024

Thanks for the additional instructions. I now have Occupancy showing up in HA for the sensor which is the most important. I guess I will wait until other controls and sensors are added. I am most appreciative!

Is it working as expected? If so, the rest of them should be fairly easy to add, just a matter of working through them.

@Rabman416
Copy link
Author

Rabman416 commented Dec 12, 2024 via email

@Itay1787
Copy link

Just place the following in a file named tuya_TZE200_ya4ft0w4.py and then place that file in your custom quirks directory, usually something like config/custom_zha_quirks, the directory would be configured in configuration.yaml.

from zhaquirks.tuya.builder import TuyaQuirkBuilder
from zhaquirks.tuya import TuyaLocalCluster
from zigpy.zcl.clusters.measurement import OccupancySensing

class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
    """Tuya local OccupancySensing cluster."""

(
    TuyaQuirkBuilder("_TZE200_ya4ft0w4", "TS0601")
    .tuya_dp(
        dp_id=1,
        ep_attribute=TuyaOccupancySensing.ep_attribute,
        attribute_name=OccupancySensing.AttributeDefs.occupancy.name,
        converter=lambda x: True if x in (1, 2) else False,
    )
    .adds(TuyaOccupancySensing) 
    .skip_configuration()
    .add_to_registry()
)

It works for me too!
Unfortunately, I don't know how to convert from z2m or the files of the other models that will work with it.
The other models are the same product, just a different model number.
I tried to look in the docs but I didn't understand anything.
I'm currently trying to do something with ChatGPT, but even that, at least at the moment, turns out not to help

@Rabman416
Copy link
Author

Just place the following in a file named tuya_TZE200_ya4ft0w4.py and then place that file in your custom quirks directory, usually something like config/custom_zha_quirks, the directory would be configured in configuration.yaml.

from zhaquirks.tuya.builder import TuyaQuirkBuilder
from zhaquirks.tuya import TuyaLocalCluster
from zigpy.zcl.clusters.measurement import OccupancySensing

class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
    """Tuya local OccupancySensing cluster."""

(
    TuyaQuirkBuilder("_TZE200_ya4ft0w4", "TS0601")
    .tuya_dp(
        dp_id=1,
        ep_attribute=TuyaOccupancySensing.ep_attribute,
        attribute_name=OccupancySensing.AttributeDefs.occupancy.name,
        converter=lambda x: True if x in (1, 2) else False,
    )
    .adds(TuyaOccupancySensing) 
    .skip_configuration()
    .add_to_registry()
)

It works for me too! Unfortunately, I don't know how to convert from z2m or the files of the other models that will work with it. The other models are the same product, just a different model number. I tried to look in the docs but I didn't understand anything. I'm currently trying to do something with ChatGPT, but even that, at least at the moment, turns out not to help

Let me know if you have any success.

@prairiesnpr
Copy link
Contributor

prairiesnpr commented Dec 13, 2024

from zigpy.quirks.v2 import EntityType
from zigpy.quirks.v2.homeassistant import UnitOfLength, UnitOfTime
from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass
import zigpy.types as t
from zigpy.zcl.clusters.measurement import IlluminanceMeasurement, OccupancySensing

from zhaquirks.tuya import TuyaLocalCluster
from zhaquirks.tuya.builder import TuyaQuirkBuilder


class TuyaIlluminanceCluster(IlluminanceMeasurement, TuyaLocalCluster):
    """Tuya Illuminance cluster."""


class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
    """Tuya local OccupancySensing cluster."""


(
    TuyaQuirkBuilder("_TZE200_ya4ft0w4", "TS0601")
    .tuya_dp(
        dp_id=1,
        ep_attribute=TuyaOccupancySensing.ep_attribute,
        attribute_name=OccupancySensing.AttributeDefs.occupancy.name,
        converter=lambda x: True if x in (1, 2) else False,
    )
    .adds(TuyaOccupancySensing)
    .tuya_number(
        dp_id=2,
        attribute_name="move_sensitivity",
        type=t.uint16_t,
        min_value=0,
        max_value=10,
        step=1,
        translation_key="move_sensitivity",
        fallback_name="Motion sensitivity",
    )
    .tuya_number(
        dp_id=3,
        attribute_name="detection_distance_min",
        type=t.uint16_t,
        device_class=SensorDeviceClass.DISTANCE,
        unit=UnitOfLength.METERS,
        min_value=0,
        max_value=8.25,
        step=0.75,
        translation_key="detection_distance_min",
        fallback_name="Minimum range",
    )
    .tuya_number(
        dp_id=4,
        attribute_name="detection_distance_max",
        type=t.uint16_t,
        device_class=SensorDeviceClass.DISTANCE,
        unit=UnitOfLength.METERS,
        min_value=0.75,
        max_value=9.0,
        step=0.75,
        translation_key="detection_distance_max",
        fallback_name="Maximum range",
    )
    .tuya_sensor(
        dp_id=9,
        attribute_name="distance",
        type=t.uint16_t,
        divisor=10,
        state_class=SensorStateClass.MEASUREMENT,
        device_class=SensorDeviceClass.DISTANCE,
        unit=UnitOfLength.METERS,
        entity_type=EntityType.STANDARD,
        translation_key="distance",
        fallback_name="Target distance",
    )
    .tuya_switch(
        dp_id=101,
        attribute_name="find_switch",
        entity_type=EntityType.STANDARD,
        translation_key="find_switch",
        fallback_name="Distance switch",
    )
    .tuya_number(
        dp_id=102,
        attribute_name="presence_sensitivity",
        type=t.uint16_t,
        min_value=0,
        max_value=10,
        step=1,
        translation_key="presence_sensitivity",
        fallback_name="Presence sensitivity",
    )
    .tuya_dp(
        dp_id=103,
        ep_attribute=TuyaIlluminanceCluster.ep_attribute,
        attribute_name=TuyaIlluminanceCluster.AttributeDefs.measured_value.name,
    )
    .adds(TuyaIlluminanceCluster)
    .tuya_number(
        dp_id=105,
        attribute_name="presence_timeout",
        type=t.uint16_t,
        device_class=SensorDeviceClass.DURATION,
        unit=UnitOfTime.SECONDS,
        min_value=1,
        max_value=15000,
        step=1,
        translation_key="presence_timeout",
        fallback_name="Fade time",
    )
    .skip_configuration()
    .add_to_registry()
)

@Rabman416
Copy link
Author

Unfortunately it is not working. ZHA fails to load.

image

This is an excerpt of some of the Log file if this helps.

2024-12-13 18:46:39.464 WARNING (SyncWorker_7) [zhaquirks] Loaded custom quirks. Please contribute them to https://github.com/zigpy/zha-device-handlers
2024-12-13 18:46:39.467 WARNING (MainThread) [zigpy.config.validators] The ikea_provider key is deprecated, migrate your configuration to the extra_providers list instead: extra_providers: [{'type': 'ikea'}]
2024-12-13 18:46:39.737 ERROR (MainThread) [homeassistant.config_entries] Error setting up entry Sonoff Zigbee 3.0 USB Dong - /dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_e82ffa45aac9eb11b0058f4f1d69213e-if00-port0, s/n: e82ffa45aac9eb11b0058f4f1d69213e - ITead - 10C4:EA60 for zha
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/config_entries.py", line 640, in __async_setup_with_context
result = await component.async_setup_entry(hass, self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/zha/init.py", line 132, in async_setup_entry
async with radio_mgr.connect_zigpy_app() as app:
~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/usr/local/lib/python3.13/contextlib.py", line 214, in aenter
return await anext(self.gen)
^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/zha/radio_manager.py", line 182, in connect_zigpy_app
app = await self.radio_type.controller.new(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
app_config, auto_form=False, start_radio=False
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/usr/local/lib/python3.13/site-packages/zigpy/application.py", line 239, in new
await app._load_db()
File "/usr/local/lib/python3.13/site-packages/zigpy/application.py", line 111, in _load_db
await self._dblistener.load()
File "/usr/local/lib/python3.13/site-packages/zigpy/appdb.py", line 684, in load
device = zigpy.quirks.get_device(device)
File "/usr/local/lib/python3.13/site-packages/zigpy/quirks/init.py", line 43, in get_device
return _DEVICE_REGISTRY.get_device(device)
~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
File "/usr/local/lib/python3.13/site-packages/zigpy/quirks/registry.py", line 130, in get_device
return quirk_entry.create_device(device)
~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
File "/usr/local/lib/python3.13/site-packages/zigpy/quirks/v2/init.py", line 429, in create_device
return CustomDeviceV2(device.application, device.ieee, device.nwk, device, self)
File "/usr/local/lib/python3.13/site-packages/zigpy/quirks/v2/init.py", line 96, in init
replace_meta(self)
~~~~~~~~~~~~^^^^^^
File "/usr/local/lib/python3.13/site-packages/zigpy/quirks/v2/init.py", line 212, in call
self.add(device)
~~~~~~~~^^^^^^^^
File "/usr/local/lib/python3.13/site-packages/zigpy/quirks/v2/init.py", line 173, in call
cluster = self.cluster(endpoint, is_server=is_server_cluster)
File "/usr/local/lib/python3.13/site-packages/zhaquirks/tuya/mcu/init.py", line 209, in init
super().init(*args, **kwargs)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.13/site-packages/zhaquirks/tuya/init.py", line 1531, in init
cluster = getattr(endpoint, dp_map.ep_attribute, None)
TypeError: attribute name must be string, not 'type'

@Itay1787
Copy link

Hi, first of all, thanks for the help!
Unfortunately, it doesn't work for me either.
This is the logs:

Log 1:

`Logger: homeassistant.config_entries
Source: config_entries.py:635
First occurred: 01:53:24 (1 occurrences)
Last logged: 01:53:24

Error setting up entry Zigbee-Hub for zha
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/config_entries.py", line 635, in __async_setup_with_context
result = await component.async_setup_entry(hass, self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/zha/init.py", line 132, in async_setup_entry
async with radio_mgr.connect_zigpy_app() as app:
File "/usr/local/lib/python3.12/contextlib.py", line 210, in aenter
return await anext(self.gen)
^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/zha/radio_manager.py", line 182, in connect_zigpy_app
app = await self.radio_type.controller.new(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/zigpy/application.py", line 238, in new
await app._load_db()
File "/usr/local/lib/python3.12/site-packages/zigpy/application.py", line 110, in _load_db
await self._dblistener.load()
File "/usr/local/lib/python3.12/site-packages/zigpy/appdb.py", line 684, in load
device = zigpy.quirks.get_device(device)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/zigpy/quirks/init.py", line 43, in get_device
return _DEVICE_REGISTRY.get_device(device)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/zigpy/quirks/registry.py", line 130, in get_device
return quirk_entry.create_device(device)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/zigpy/quirks/v2/init.py", line 429, in create_device
return CustomDeviceV2(device.application, device.ieee, device.nwk, device, self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/zigpy/quirks/v2/init.py", line 96, in init
replace_meta(self)
File "/usr/local/lib/python3.12/site-packages/zigpy/quirks/v2/init.py", line 212, in call
self.add(device)
File "/usr/local/lib/python3.12/site-packages/zigpy/quirks/v2/init.py", line 173, in call
cluster = self.cluster(endpoint, is_server=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/zhaquirks/tuya/mcu/init.py", line 209, in init
super().init(*args, **kwargs)
File "/usr/local/lib/python3.12/site-packages/zhaquirks/tuya/init.py", line 1529, in init
cluster = getattr(endpoint, dp_map.ep_attribute, None)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: attribute name must be string, not 'type'`

Log 2:

`Error setting up entry Zigbee-Hub for zha

Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/config_entries.py", line 635, in __async_setup_with_context
result = await component.async_setup_entry(hass, self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/zha/init.py", line 132, in async_setup_entry
async with radio_mgr.connect_zigpy_app() as app:
File "/usr/local/lib/python3.12/contextlib.py", line 210, in aenter
return await anext(self.gen)
^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/zha/radio_manager.py", line 182, in connect_zigpy_app
app = await self.radio_type.controller.new(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/zigpy/application.py", line 238, in new
await app._load_db()
File "/usr/local/lib/python3.12/site-packages/zigpy/application.py", line 110, in _load_db
await self._dblistener.load()
File "/usr/local/lib/python3.12/site-packages/zigpy/appdb.py", line 684, in load
device = zigpy.quirks.get_device(device)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/zigpy/quirks/init.py", line 43, in get_device
return _DEVICE_REGISTRY.get_device(device)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/zigpy/quirks/registry.py", line 125, in get_device
raise MultipleQuirksMatchException(
zigpy.exceptions.MultipleQuirksMatchException: Multiple matches found for device : [QuirksV2RegistryEntry(quirk_file=PosixPath('/usr/local/lib/python3.12/site-packages/zhaquirks/tuya/builder/init.py'), quirk_file_line=75, manufacturer_model_metadata=(ManufacturerModelMetadata(manufacturer='_TZE200_ya4ft0w4', model='TS0601'),), friendly_name=None, device_alerts=(), filters=(), custom_device_class=None, device_node_descriptor=None, skip_device_configuration=True, adds_metadata=(AddsMetadata(cluster=<class 'tuya_motion_sensor_ya4ft0w4.TuyaIlluminanceCluster'>, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, constant_attributes=frozendict.frozendict({})),), removes_metadata=(), replaces_metadata=(ReplacesMetadata(remove=RemovesMetadata(cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>), add=AddsMetadata(cluster=<class 'zhaquirks.tuya.builder.TuyaQuirkBuilder.add_to_registry..TuyaReplacementCluster'>, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, constant_attributes=frozendict.frozendict({}))),), replaces_cluster_occurrences_metadata=(), entity_metadata=(NumberMetadata(entity_platform=<EntityPlatform.NUMBER: 'number'>, entity_type=<EntityType.CONFIG: 'config'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='move_sensitivity', fallback_name='Motion sensitivity', attribute_name='move_sensitivity', reporting_config=None, min=0, max=10, step=1, unit=None, mode=None, multiplier=None, device_class=None), NumberMetadata(entity_platform=<EntityPlatform.NUMBER: 'number'>, entity_type=<EntityType.CONFIG: 'config'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='detection_distance_min', fallback_name='Minimum range', attribute_name='detection_distance_min', reporting_config=None, min=0, max=8.25, step=0.75, unit=<UnitOfLength.METERS: 'm'>, mode=None, multiplier=None, device_class=<SensorDeviceClass.DISTANCE: 'distance'>), NumberMetadata(entity_platform=<EntityPlatform.NUMBER: 'number'>, entity_type=<EntityType.CONFIG: 'config'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='detection_distance_max', fallback_name='Maximum range', attribute_name='detection_distance_max', reporting_config=None, min=0.75, max=9.0, step=0.75, unit=<UnitOfLength.METERS: 'm'>, mode=None, multiplier=None, device_class=<SensorDeviceClass.DISTANCE: 'distance'>), ZCLSensorMetadata(entity_platform=<EntityPlatform.SENSOR: 'sensor'>, entity_type=<EntityType.STANDARD: 'standard'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='distance', fallback_name='Target distance', attribute_name='distance', reporting_config=None, divisor=10, multiplier=1, unit=<UnitOfLength.METERS: 'm'>, device_class=<SensorDeviceClass.DISTANCE: 'distance'>, state_class=<SensorStateClass.MEASUREMENT: 'measurement'>), SwitchMetadata(entity_platform=<EntityPlatform.SWITCH: 'switch'>, entity_type=<EntityType.STANDARD: 'standard'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='find_switch', fallback_name='Distance switch', attribute_name='find_switch', reporting_config=None, force_inverted=False, invert_attribute_name=None, off_value=0, on_value=1), NumberMetadata(entity_platform=<EntityPlatform.NUMBER: 'number'>, entity_type=<EntityType.CONFIG: 'config'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='presence_sensitivity', fallback_name='Presence sensitivity', attribute_name='presence_sensitivity', reporting_config=None, min=0, max=10, step=1, unit=None, mode=None, multiplier=None, device_class=None), NumberMetadata(entity_platform=<EntityPlatform.NUMBER: 'number'>, entity_type=<EntityType.CONFIG: 'config'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='presence_timeout', fallback_name='Fade time', attribute_name='presence_timeout', reporting_config=None, min=1, max=15000, step=1, unit=<UnitOfTime.SECONDS: 's'>, mode=None, multiplier=None, device_class=<SensorDeviceClass.DURATION: 'duration'>)), device_automation_triggers_metadata=frozendict.frozendict({})), QuirksV2RegistryEntry(quirk_file=PosixPath('/usr/local/lib/python3.12/site-packages/zhaquirks/tuya/builder/init.py'), quirk_file_line=75, manufacturer_model_metadata=(ManufacturerModelMetadata(manufacturer='_TZE200_ya4ft0w4', model='TS0601'),), friendly_name=None, device_alerts=(), filters=(), custom_device_class=None, device_node_descriptor=None, skip_device_configuration=True, adds_metadata=(AddsMetadata(cluster=<class 'tuya_motion_sensor_ya4ft0w4.TuyaIlluminanceCluster'>, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, constant_attributes=frozendict.frozendict({})),), removes_metadata=(), replaces_metadata=(ReplacesMetadata(remove=RemovesMetadata(cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>), add=AddsMetadata(cluster=<class 'zhaquirks.tuya.builder.TuyaQuirkBuilder.add_to_registry..TuyaReplacementCluster'>, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, constant_attributes=frozendict.frozendict({}))),), replaces_cluster_occurrences_metadata=(), entity_metadata=(NumberMetadata(entity_platform=<EntityPlatform.NUMBER: 'number'>, entity_type=<EntityType.CONFIG: 'config'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='move_sensitivity', fallback_name='Motion sensitivity', attribute_name='move_sensitivity', reporting_config=None, min=0, max=10, step=1, unit=None, mode=None, multiplier=None, device_class=None), NumberMetadata(entity_platform=<EntityPlatform.NUMBER: 'number'>, entity_type=<EntityType.CONFIG: 'config'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='detection_distance_min', fallback_name='Minimum range', attribute_name='detection_distance_min', reporting_config=None, min=0, max=8.25, step=0.75, unit=<UnitOfLength.METERS: 'm'>, mode=None, multiplier=None, device_class=<SensorDeviceClass.DISTANCE: 'distance'>), NumberMetadata(entity_platform=<EntityPlatform.NUMBER: 'number'>, entity_type=<EntityType.CONFIG: 'config'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='detection_distance_max', fallback_name='Maximum range', attribute_name='detection_distance_max', reporting_config=None, min=0.75, max=9.0, step=0.75, unit=<UnitOfLength.METERS: 'm'>, mode=None, multiplier=None, device_class=<SensorDeviceClass.DISTANCE: 'distance'>), ZCLSensorMetadata(entity_platform=<EntityPlatform.SENSOR: 'sensor'>, entity_type=<EntityType.STANDARD: 'standard'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='distance', fallback_name='Target distance', attribute_name='distance', reporting_config=None, divisor=10, multiplier=1, unit=<UnitOfLength.METERS: 'm'>, device_class=<SensorDeviceClass.DISTANCE: 'distance'>, state_class=<SensorStateClass.MEASUREMENT: 'measurement'>), SwitchMetadata(entity_platform=<EntityPlatform.SWITCH: 'switch'>, entity_type=<EntityType.STANDARD: 'standard'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='find_switch', fallback_name='Distance switch', attribute_name='find_switch', reporting_config=None, force_inverted=False, invert_attribute_name=None, off_value=0, on_value=1), NumberMetadata(entity_platform=<EntityPlatform.NUMBER: 'number'>, entity_type=<EntityType.CONFIG: 'config'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='presence_sensitivity', fallback_name='Presence sensitivity', attribute_name='presence_sensitivity', reporting_config=None, min=0, max=10, step=1, unit=None, mode=None, multiplier=None, device_class=None), NumberMetadata(entity_platform=<EntityPlatform.NUMBER: 'number'>, entity_type=<EntityType.CONFIG: 'config'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='presence_timeout', fallback_name='Fade time', attribute_name='presence_timeout', reporting_config=None, min=1, max=15000, step=1, unit=<UnitOfTime.SECONDS: 's'>, mode=None, multiplier=None, device_class=<SensorDeviceClass.DURATION: 'duration'>)), device_automation_triggers_metadata=frozendict.frozendict({}))]`

@prairiesnpr
Copy link
Contributor

I missed ep_attribute on 103, edited above, see if that works. I don't have the device.

@Rabman416
Copy link
Author

Making progress. Occupancy is no longer working.

image

@prairiesnpr
Copy link
Contributor

Try the updated version above

@Rabman416
Copy link
Author

I added this and it now appears to work:

image

@Itay1787
Copy link

Itay1787 commented Dec 14, 2024

Weirdly, I don't have the same sensors and controls in Home Assistance as you do
Also, the Illuminance Sensor is not working as expected only 1 lx

תמונה

Making progress. Occupancy is no longer working.

image

@Itay1787
Copy link

Hi, I found more log that may explain why I don't have all the controls

`Logger: zha.application.gateway
Source: /usr/local/lib/python3.12/site-packages/zha/application/gateway.py:339
First occurred: 02:28:48 (3 occurrences)
Last logged: 02:28:48

Failed to create platform entity: <class 'zha.application.platforms.number.NumberConfigurationEntity'> [args=('e4:98:bb:43:00:8d:58:22-1', [<zha.zigbee.cluster_handlers.manufacturerspecific.TuyaClusterHandler object at 0x7f31abd456d0>], <zha.zigbee.endpoint.Endpoint object at 0x7f31abd45490>, - quirk_applied: True - quirk_or_device_class: zigpy.quirks.v2.CustomDeviceV2 - quirk_id: None), kwargs={'entity_metadata': NumberMetadata(entity_platform=<EntityPlatform.NUMBER: 'number'>, entity_type=<EntityType.CONFIG: 'config'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='detection_distance_min', fallback_name='Minimum range', attribute_name='detection_distance_min', reporting_config=None, min=0, max=8.25, step=0.75, unit=<UnitOfLength.METERS: 'm'>, mode=None, multiplier=None, device_class=<SensorDeviceClass.DISTANCE: 'distance'>)}]
Failed to create platform entity: <class 'zha.application.platforms.number.NumberConfigurationEntity'> [args=('e4:98:bb:43:00:8d:58:22-1', [<zha.zigbee.cluster_handlers.manufacturerspecific.TuyaClusterHandler object at 0x7f31abd456d0>], <zha.zigbee.endpoint.Endpoint object at 0x7f31abd45490>, - quirk_applied: True - quirk_or_device_class: zigpy.quirks.v2.CustomDeviceV2 - quirk_id: None), kwargs={'entity_metadata': NumberMetadata(entity_platform=<EntityPlatform.NUMBER: 'number'>, entity_type=<EntityType.CONFIG: 'config'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='detection_distance_max', fallback_name='Maximum range', attribute_name='detection_distance_max', reporting_config=None, min=0.75, max=9.0, step=0.75, unit=<UnitOfLength.METERS: 'm'>, mode=None, multiplier=None, device_class=<SensorDeviceClass.DISTANCE: 'distance'>)}]
Failed to create platform entity: <class 'zha.application.platforms.sensor.Sensor'> [args=('e4:98:bb:43:00:8d:58:22-1', [<zha.zigbee.cluster_handlers.manufacturerspecific.TuyaClusterHandler object at 0x7f31abd456d0>], <zha.zigbee.endpoint.Endpoint object at 0x7f31abd45490>, - quirk_applied: True - quirk_or_device_class: zigpy.quirks.v2.CustomDeviceV2 - quirk_id: None), kwargs={'entity_metadata': ZCLSensorMetadata(entity_platform=<EntityPlatform.SENSOR: 'sensor'>, entity_type=<EntityType.STANDARD: 'standard'>, cluster_id=61184, endpoint_id=1, cluster_type=<ClusterType.Server: 0>, initially_disabled=False, attribute_initialized_from_cache=True, translation_key='distance', fallback_name='Target distance', attribute_name='distance', reporting_config=None, divisor=10, multiplier=1, unit=<UnitOfLength.METERS: 'm'>, device_class=<SensorDeviceClass.DISTANCE: 'distance'>, state_class=<SensorStateClass.MEASUREMENT: 'measurement'>)}]
Traceback (most recent call last):
File "/usr/local/lib/python3.12/site-packages/zha/application/gateway.py", line 335, in create_platform_entities
platform_entity = platform_entity_class.create_platform_entity(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/zha/application/platforms/number/init.py", line 229, in create_platform_entity
return cls(unique_id, cluster_handlers, endpoint, device, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/zha/application/platforms/number/init.py", line 242, in init
super().init(unique_id, cluster_handlers, endpoint, device, **kwargs)
File "/usr/local/lib/python3.12/site-packages/zha/application/platforms/init.py", line 297, in init
self._init_from_quirks_metadata(entity_metadata)
File "/usr/local/lib/python3.12/site-packages/zha/application/platforms/number/init.py", line 269, in _init_from_quirks_metadata
self._attr_native_unit_of_measurement = validate_unit(
^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/zha/units.py", line 167, in validate_unit
return UNITS_OF_MEASUREtype(external_unit).name
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyError: 'UnitOfLength'`

@Rabman416
Copy link
Author

Glad you understand why. I sure don’t.

Thanks to you both for helping out!

@prairiesnpr
Copy link
Contributor

Please make sure you are on the latest HA. I'm seeing python 3.12 in your logs and I would expect 3.13.

@Itay1787
Copy link

Please make sure you are on the latest HA. I'm seeing python 3.12 in your logs and I would expect 3.13.

Yup😅 you are correct I will update HA and check again

@Itay1787
Copy link

Please make sure you are on the latest HA. I'm seeing python 3.12 in your logs and I would expect 3.13.

Ok, I updated HA and now I have all the controls. but Illuminance is not working as expected it only shows 1 lx.
and control for the Maximum range doesn't work. I won't let me change it, after I tried to make a change to this setting it got back to the max of 150m (or setting 9)
and Minimum range also seems to not working as expected if I make a change it tries to apply it but goes back to 0m but I don't know if it is set for 0m on the device or if it is HA reading problem
I don't see anything in the log about this.

@Rabman416
Copy link
Author

Maybe try switching the stuff in the mfgr app

@Itay1787
Copy link

Maybe try switching the stuff in the mfgr app

Sorry, in what app?

@prairiesnpr
Copy link
Contributor

Ok, I updated HA and now I have all the controls. but Illuminance is not working as expected it only shows 1 lx. and control for the Maximum range doesn't work. I won't let me change it, after I tried to make a change to this setting it got back to the max of 150m (or setting 9) and Minimum range also seems to not working as expected if I make a change it tries to apply it but goes back to 0m but I don't know if it is set for 0m on the device or if it is HA reading problem I don't see anything in the log about this.

There are a few options here, if it's working for @Rabman416, then it's possible you have a defective device, does it work when paired to a Tuya hub? It's also possible that the devices have different firmware and don't use the same DPs. The quirk above is based on the z2m converter, and should work the same, I did see that there are widely reported issues with the illuminance measurement for this sensor though.

You can enable debug logging in ZHA, note the start and end timestamps and the device ieee and nwk, run it through all of its paces, then attach the logs and we can see if there is anything logged.

@Rabman416
Copy link
Author

Sorry forget about my comment about app. I am also trying to get the wifi version working. I am using the Smart Life app for it and the TUYA integration.

Maximum range works but looks like it needs to be divide by 100.

Sliders do change and stay where I set them.

@Itay1787
Copy link

There are a few options here, if it's working for @Rabman416, then it's possible you have a defective device, does it work when paired to a Tuya hub? It's also possible that the devices have different firmware and don't use the same DPs. The quirk above is based on the z2m converter, and should work the same, I did see that there are widely reported issues with the illuminance measurement for this sensor though.

You can enable debug logging in ZHA, note the start and end timestamps and the device ieee and nwk, run it through all of its paces, then attach the logs and we can see if there is anything logged.

Yes, it is working as I expected in the Tuya app with a Tuya hub, the illuminance, and all the controls.
Yup, I also saw reports about the illuminance measurement but because in the Tuya app, it was working I thought that maybe they fixed it with this version of the sensor.

@Itay1787
Copy link

Sliders do change and stay where I set them.

all the sliders??
and illuminance with ZHA is working?
can you share what version of the sensor you have?

@prairiesnpr
Copy link
Contributor

Maximum range works but looks like it needs to be divide by 100.

Add multiplier=0.1, to that .tuya_number

@Rabman416
Copy link
Author

multiplier=0.1,

My mistake on this. It was the Wifi version, not the Zigbee.

@Rabman416
Copy link
Author

Sliders do change and stay where I set them.

all the sliders?? and illuminance with ZHA is working? can you share what version of the sensor you have?

All the sliders work. The illumination sensor shows only 1 or 2. Some other posts indicated the hole in the sensor needed to be enlarged. Never tried it though.

My model is a Loginovo ZY-M100-24G

@Itay1787
Copy link

Itay1787 commented Dec 14, 2024

All the sliders work. The illumination sensor shows only 1 or 2. Some other posts indicated the hole in the sensor needed to be enlarged. Never tried it though.

My model is a Loginovo ZY-M100-24G

Ok, then the illumination sensor acts the same as mine. but in the Tuya app, it works like normal so it is a ZHA problem.

I don't get why then the Maximum range sliders don't work in mine.
also, the Minimum range acts up it stays sometimes and sometimes it goes to 0
but Minimum range and Minimum range changes do log, in the logbook like the other control upon changing

we have the same model

@prairiesnpr
Copy link
Contributor

Ok, then the illumination sensor acts the same as mine. but in the Tuya app, it works like normal so it is a ZHA problem.

I'm thinking that it's not scaled correctly, there isn't any scaling in z2m either, but looking at the other Tuya quirk, they use 10000 * math.log10(x) + 1 if x != 0 else 0

You might try the following and see if it looks better, otherwise you would need to reverse engineer the scaling.

import math
from zigpy.quirks.v2 import EntityType
from zigpy.quirks.v2.homeassistant import UnitOfLength, UnitOfTime
from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass
import zigpy.types as t
from zigpy.zcl.clusters.measurement import IlluminanceMeasurement, OccupancySensing

from zhaquirks.tuya import TuyaLocalCluster
from zhaquirks.tuya.builder import TuyaQuirkBuilder


class TuyaIlluminanceCluster(IlluminanceMeasurement, TuyaLocalCluster):
    """Tuya Illuminance cluster."""


class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
    """Tuya local OccupancySensing cluster."""


(
    TuyaQuirkBuilder("_TZE200_ya4ft0w4", "TS0601")
    .tuya_dp(
        dp_id=1,
        ep_attribute=TuyaOccupancySensing.ep_attribute,
        attribute_name=OccupancySensing.AttributeDefs.occupancy.name,
        converter=lambda x: True if x in (1, 2) else False,
    )
    .adds(TuyaOccupancySensing)
    .tuya_number(
        dp_id=2,
        attribute_name="move_sensitivity",
        type=t.uint16_t,
        min_value=0,
        max_value=10,
        step=1,
        translation_key="move_sensitivity",
        fallback_name="Motion sensitivity",
    )
    .tuya_number(
        dp_id=3,
        attribute_name="detection_distance_min",
        type=t.uint16_t,
        device_class=SensorDeviceClass.DISTANCE,
        unit=UnitOfLength.METERS,
        min_value=0,
        max_value=8.25,
        step=0.75,
        translation_key="detection_distance_min",
        fallback_name="Minimum range",
    )
    .tuya_number(
        dp_id=4,
        attribute_name="detection_distance_max",
        type=t.uint16_t,
        device_class=SensorDeviceClass.DISTANCE,
        unit=UnitOfLength.METERS,
        min_value=0.75,
        max_value=9.0,
        step=0.75,
        translation_key="detection_distance_max",
        fallback_name="Maximum range",
    )
    .tuya_sensor(
        dp_id=9,
        attribute_name="distance",
        type=t.uint16_t,
        divisor=10,
        state_class=SensorStateClass.MEASUREMENT,
        device_class=SensorDeviceClass.DISTANCE,
        unit=UnitOfLength.METERS,
        entity_type=EntityType.STANDARD,
        translation_key="distance",
        fallback_name="Target distance",
    )
    .tuya_switch(
        dp_id=101,
        attribute_name="find_switch",
        entity_type=EntityType.STANDARD,
        translation_key="find_switch",
        fallback_name="Distance switch",
    )
    .tuya_number(
        dp_id=102,
        attribute_name="presence_sensitivity",
        type=t.uint16_t,
        min_value=0,
        max_value=10,
        step=1,
        translation_key="presence_sensitivity",
        fallback_name="Presence sensitivity",
    )
    .tuya_dp(
        dp_id=103,
        ep_attribute=TuyaIlluminanceCluster.ep_attribute,
        attribute_name=TuyaIlluminanceCluster.AttributeDefs.measured_value.name,
        converter=lambda x: 10000 * math.log10(x) + 1 if x != 0 else 0,
    )
    .adds(TuyaIlluminanceCluster)
    .tuya_number(
        dp_id=105,
        attribute_name="presence_timeout",
        type=t.uint16_t,
        device_class=SensorDeviceClass.DURATION,
        unit=UnitOfTime.SECONDS,
        min_value=1,
        max_value=15000,
        step=1,
        translation_key="presence_timeout",
        fallback_name="Fade time",
    )
    .skip_configuration()
    .add_to_registry()
)

@Rabman416
Copy link
Author

OK, I’ll have to try it out this evening. Thanks for all your hard work.

@prairiesnpr prairiesnpr linked a pull request Dec 14, 2024 that will close this issue
3 tasks
@Rabman416
Copy link
Author

Well it is presenting a higher number now....no clue as to whether it is correct or not. Better than 1-2 lx though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants