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

[Device Support Request] Tuya soil moisture sensor C3007 / ZG-303Z / TS0601 by _TZE200_wqashyqo

Open roggy85 opened this issue 10 months ago • 34 comments

Problem description

Hi, I got two Zigbee soil moisture sensors with the Model names C3007 / ZG-303Z. I can add them to ZHA without problems but the main sensor is missing - the soil moisture. I get Humidity/Temperature and Battery sensors/readings but can't see anything about the soil.

The device info identifies the devices as TS0601 by _TZE200_wqashyqo

Any hints are welcome how I could get the sensor to run properly.

many thanks!

regards

Solution description

Getting the Sensor readings for the soil moisture.

Screenshots/Video

Screenshots/Video Image Image

Device signature

Device signature

{
  "node_descriptor": {
    "logical_type": 2,
    "complex_descriptor_available": 0,
    "user_descriptor_available": 0,
    "reserved": 0,
    "aps_flags": 0,
    "frequency_band": 8,
    "mac_capability_flags": 128,
    "manufacturer_code": 4742,
    "maximum_buffer_size": 66,
    "maximum_incoming_transfer_size": 66,
    "server_mask": 10752,
    "maximum_outgoing_transfer_size": 66,
    "descriptor_capability_field": 0
  },
  "endpoints": {
    "1": {
      "profile_id": "0x0104",
      "device_type": "0x0302",
      "input_clusters": [
        "0x0000",
        "0x0001",
        "0x0003",
        "0x0402",
        "0x0405"
      ],
      "output_clusters": [
        "0x0003"
      ]
    }
  },
  "manufacturer": "_TZE200_wqashyqo",
  "model": "TS0601",
  "class": "zigpy.device.Device"
}

Diagnostic information

Diagnostic information

{
  "home_assistant": {
    "installation_type": "Home Assistant Container",
    "version": "2025.5.3",
    "dev": false,
    "hassio": false,
    "virtualenv": false,
    "python_version": "3.13.3",
    "docker": true,
    "arch": "x86_64",
    "timezone": "Europe/Berlin",
    "os_name": "Linux",
    "os_version": "6.14.9-300.fc42.x86_64",
    "run_as_root": true
  },
  "custom_components": {
    "dewpoint": {
      "documentation": "https://github.com/miguelangel-nubla/home-assistant-dewpoint",
      "version": "1.1.0",
      "requirements": [
        "psychrolib==2.1.0"
      ]
    },
    "ecoflow": {
      "documentation": "https://github.com/vwt12eh8/hassio-ecoflow",
      "version": "2.1",
      "requirements": [
        "reactivex"
      ]
    },
    "homematicip_local": {
      "documentation": "https://github.com/danielperna84/custom_homematic",
      "version": "1.69.0",
      "requirements": [
        "hahomematic==2024.10.17"
      ]
    },
    "ics_calendar": {
      "documentation": "https://github.com/franc6/ics_calendar",
      "version": "5.1.0",
      "requirements": [
        "icalendar~=6.1",
        "python-dateutil>=2.9.0.post0",
        "pytz>=2024.1",
        "recurring_ical_events~=3.3,>=3.3.4",
        "ics>=0.7.2",
        "arrow",
        "httpx_auth>=0.22.0"
      ]
    },
    "hacs": {
      "documentation": "https://hacs.xyz/docs/use/",
      "version": "2.0.5",
      "requirements": [
        "aiogithubapi>=22.10.1"
      ]
    },
    "waste_collection_schedule": {
      "documentation": "https://github.com/mampfes/hacs_waste_collection_schedule#readme",
      "version": "2.8.0",
      "requirements": [
        "icalendar",
        "icalevents>=0.1.26,!=0.1.28",
        "beautifulsoup4",
        "lxml",
        "pycryptodome",
        "pypdf"
      ]
    },
    "dawarich": {
      "documentation": "https://github.com/AlbinLind/dawarich-home-assistant",
      "version": "0.8.3",
      "requirements": [
        "dawarich-api==0.4.1"
      ]
    }
  },
  "integration_manifest": {
    "domain": "zha",
    "name": "Zigbee Home Automation",
    "after_dependencies": [
      "hassio",
      "onboarding",
      "usb"
    ],
    "codeowners": [
      "dmulcahey",
      "adminiuga",
      "puddly",
      "TheJulianJES"
    ],
    "config_flow": true,
    "dependencies": [
      "file_upload",
      "homeassistant_hardware"
    ],
    "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": [
      "zha==0.0.57"
    ],
    "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": 6.48999703116715e-05
    },
    "01JRAQDX9M2FJXDR9FT2M1QBKP": {
      "wait_import_platforms": -0.02154727902961895,
      "wait_base_component": -0.004239324014633894,
      "config_entry_setup": 4.445135774964001
    }
  },
  "data": {
    "version": 1,
    "ieee": "**REDACTED**",
    "nwk": "0x4436",
    "manufacturer": "_TZE200_wqashyqo",
    "model": "TS0601",
    "friendly_manufacturer": "_TZE200_wqashyqo",
    "friendly_model": "TS0601",
    "name": "_TZE200_wqashyqo TS0601",
    "quirk_applied": false,
    "quirk_class": "zigpy.device.Device",
    "quirk_id": null,
    "manufacturer_code": 4742,
    "power_source": "Battery or Unknown",
    "lqi": 96,
    "rssi": -76,
    "last_seen": "2025-06-11T12:13:46.344978+00:00",
    "available": true,
    "device_type": "EndDevice",
    "active_coordinator": false,
    "node_descriptor": {
      "logical_type": "EndDevice",
      "complex_descriptor_available": false,
      "user_descriptor_available": false,
      "reserved": 0,
      "aps_flags": 0,
      "frequency_band": 8,
      "mac_capability_flags": 128,
      "manufacturer_code": 4742,
      "maximum_buffer_size": 66,
      "maximum_incoming_transfer_size": 66,
      "server_mask": 10752,
      "maximum_outgoing_transfer_size": 66,
      "descriptor_capability_field": 0
    },
    "endpoints": {
      "1": {
        "profile_id": 260,
        "device_type": {
          "name": "TEMPERATURE_SENSOR",
          "id": 770
        },
        "in_clusters": [
          {
            "cluster_id": "0x0000",
            "endpoint_attribute": "basic",
            "attributes": [
              {
                "id": "0x0013",
                "name": "alarm_mask",
                "zcl_type": "map8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0001",
                "name": "app_version",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0xfffd",
                "name": "cluster_revision",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0006",
                "name": "date_code",
                "zcl_type": "string",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0012",
                "name": "device_enabled",
                "zcl_type": "bool_",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0014",
                "name": "disable_local_config",
                "zcl_type": "map8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0008",
                "name": "generic_device_class",
                "zcl_type": "enum8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0009",
                "name": "generic_device_type",
                "zcl_type": "enum8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0003",
                "name": "hw_version",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0010",
                "name": "location_desc",
                "zcl_type": "string",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0004",
                "name": "manufacturer",
                "zcl_type": "string",
                "value": "_TZE200_wqashyqo",
                "unsupported": false
              },
              {
                "id": "0x000c",
                "name": "manufacturer_version_details",
                "zcl_type": "string",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0005",
                "name": "model",
                "zcl_type": "string",
                "value": "TS0601",
                "unsupported": false
              },
              {
                "id": "0x0011",
                "name": "physical_env",
                "zcl_type": "enum8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0007",
                "name": "power_source",
                "zcl_type": "enum8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x000a",
                "name": "product_code",
                "zcl_type": "octstr",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x000e",
                "name": "product_label",
                "zcl_type": "string",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x000b",
                "name": "product_url",
                "zcl_type": "string",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0xfffe",
                "name": "reporting_status",
                "zcl_type": "enum8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x000d",
                "name": "serial_number",
                "zcl_type": "string",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0002",
                "name": "stack_version",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x4000",
                "name": "sw_build_id",
                "zcl_type": "string",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0000",
                "name": "zcl_version",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              }
            ]
          },
          {
            "cluster_id": "0x0001",
            "endpoint_attribute": "power",
            "attributes": [
              {
                "id": "0x0052",
                "name": "battery_2_a_hr_rating",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0055",
                "name": "battery_2_alarm_mask",
                "zcl_type": "map8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x005e",
                "name": "battery_2_alarm_state",
                "zcl_type": "map32",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0050",
                "name": "battery_2_manufacturer",
                "zcl_type": "string",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x005a",
                "name": "battery_2_percent_min_thres",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x005b",
                "name": "battery_2_percent_thres1",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x005c",
                "name": "battery_2_percent_thres2",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x005d",
                "name": "battery_2_percent_thres3",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0041",
                "name": "battery_2_percentage_remaining",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0053",
                "name": "battery_2_quantity",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0054",
                "name": "battery_2_rated_voltage",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0051",
                "name": "battery_2_size",
                "zcl_type": "enum8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0056",
                "name": "battery_2_volt_min_thres",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0057",
                "name": "battery_2_volt_thres1",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0058",
                "name": "battery_2_volt_thres2",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0059",
                "name": "battery_2_volt_thres3",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0040",
                "name": "battery_2_voltage",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0072",
                "name": "battery_3_a_hr_rating",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0075",
                "name": "battery_3_alarm_mask",
                "zcl_type": "map8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x007e",
                "name": "battery_3_alarm_state",
                "zcl_type": "map32",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0070",
                "name": "battery_3_manufacturer",
                "zcl_type": "string",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x007a",
                "name": "battery_3_percent_min_thres",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x007b",
                "name": "battery_3_percent_thres1",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x007c",
                "name": "battery_3_percent_thres2",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x007d",
                "name": "battery_3_percent_thres3",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0061",
                "name": "battery_3_percentage_remaining",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0073",
                "name": "battery_3_quantity",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0074",
                "name": "battery_3_rated_voltage",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0071",
                "name": "battery_3_size",
                "zcl_type": "enum8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0076",
                "name": "battery_3_volt_min_thres",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0077",
                "name": "battery_3_volt_thres1",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0078",
                "name": "battery_3_volt_thres2",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0079",
                "name": "battery_3_volt_thres3",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0060",
                "name": "battery_3_voltage",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0032",
                "name": "battery_a_hr_rating",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0035",
                "name": "battery_alarm_mask",
                "zcl_type": "map8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x003e",
                "name": "battery_alarm_state",
                "zcl_type": "map32",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0030",
                "name": "battery_manufacturer",
                "zcl_type": "string",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x003a",
                "name": "battery_percent_min_thres",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x003b",
                "name": "battery_percent_thres1",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x003c",
                "name": "battery_percent_thres2",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x003d",
                "name": "battery_percent_thres3",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0021",
                "name": "battery_percentage_remaining",
                "zcl_type": "uint8",
                "value": 200,
                "unsupported": false
              },
              {
                "id": "0x0033",
                "name": "battery_quantity",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": true
              },
              {
                "id": "0x0034",
                "name": "battery_rated_voltage",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0031",
                "name": "battery_size",
                "zcl_type": "enum8",
                "value": null,
                "unsupported": true
              },
              {
                "id": "0x0036",
                "name": "battery_volt_min_thres",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0037",
                "name": "battery_volt_thres1",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0038",
                "name": "battery_volt_thres2",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0039",
                "name": "battery_volt_thres3",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0020",
                "name": "battery_voltage",
                "zcl_type": "uint8",
                "value": 30,
                "unsupported": false
              },
              {
                "id": "0xfffd",
                "name": "cluster_revision",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0010",
                "name": "mains_alarm_mask",
                "zcl_type": "map8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0001",
                "name": "mains_frequency",
                "zcl_type": "uint8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0012",
                "name": "mains_volt_max_thres",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0011",
                "name": "mains_volt_min_thres",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0000",
                "name": "mains_voltage",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0013",
                "name": "mains_voltage_dwell_trip_point",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0xfffe",
                "name": "reporting_status",
                "zcl_type": "enum8",
                "value": null,
                "unsupported": false
              }
            ]
          },
          {
            "cluster_id": "0x0003",
            "endpoint_attribute": "identify",
            "attributes": [
              {
                "id": "0xfffd",
                "name": "cluster_revision",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0000",
                "name": "identify_time",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0xfffe",
                "name": "reporting_status",
                "zcl_type": "enum8",
                "value": null,
                "unsupported": false
              }
            ]
          },
          {
            "cluster_id": "0x0402",
            "endpoint_attribute": "temperature",
            "attributes": [
              {
                "id": "0xfffd",
                "name": "cluster_revision",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0002",
                "name": "max_measured_value",
                "zcl_type": "int16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0000",
                "name": "measured_value",
                "zcl_type": "int16",
                "value": 2210,
                "unsupported": false
              },
              {
                "id": "0x0001",
                "name": "min_measured_value",
                "zcl_type": "int16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0xfffe",
                "name": "reporting_status",
                "zcl_type": "enum8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0003",
                "name": "tolerance",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              }
            ]
          },
          {
            "cluster_id": "0x0405",
            "endpoint_attribute": "humidity",
            "attributes": [
              {
                "id": "0xfffd",
                "name": "cluster_revision",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0002",
                "name": "max_measured_value",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0000",
                "name": "measured_value",
                "zcl_type": "uint16",
                "value": 300,
                "unsupported": false
              },
              {
                "id": "0x0001",
                "name": "min_measured_value",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0xfffe",
                "name": "reporting_status",
                "zcl_type": "enum8",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0003",
                "name": "tolerance",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              }
            ]
          }
        ],
        "out_clusters": [
          {
            "cluster_id": "0x0003",
            "endpoint_attribute": "identify",
            "attributes": [
              {
                "id": "0xfffd",
                "name": "cluster_revision",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0x0000",
                "name": "identify_time",
                "zcl_type": "uint16",
                "value": null,
                "unsupported": false
              },
              {
                "id": "0xfffe",
                "name": "reporting_status",
                "zcl_type": "enum8",
                "value": null,
                "unsupported": false
              }
            ]
          }
        ]
      }
    },
    "zha_lib_entities": {
      "button": [
        {
          "info_object": {
            "fallback_name": null,
            "unique_id": "**REDACTED**",
            "migrate_unique_ids": [],
            "platform": "button",
            "class_name": "IdentifyButton",
            "translation_key": null,
            "device_class": "identify",
            "state_class": null,
            "entity_category": "diagnostic",
            "entity_registry_enabled_default": true,
            "enabled": true,
            "primary": false,
            "cluster_handlers": [
              {
                "class_name": "IdentifyClusterHandler",
                "generic_id": "cluster_handler_0x0003",
                "endpoint_id": 1,
                "cluster": {
                  "id": 3,
                  "name": "Identify",
                  "type": "server"
                },
                "id": "1:0x0003",
                "unique_id": "**REDACTED**",
                "status": "INITIALIZED",
                "value_attribute": null
              }
            ],
            "device_ieee": "**REDACTED**",
            "endpoint_id": 1,
            "available": true,
            "group_id": null,
            "command": "identify",
            "args": [
              5
            ],
            "kwargs": {}
          },
          "state": {
            "class_name": "IdentifyButton",
            "available": true
          }
        }
      ],
      "sensor": [
        {
          "info_object": {
            "fallback_name": null,
            "unique_id": "**REDACTED**",
            "migrate_unique_ids": [],
            "platform": "sensor",
            "class_name": "LQISensor",
            "translation_key": "lqi",
            "device_class": null,
            "state_class": "measurement",
            "entity_category": "diagnostic",
            "entity_registry_enabled_default": false,
            "enabled": true,
            "primary": false,
            "cluster_handlers": [
              {
                "class_name": "BasicClusterHandler",
                "generic_id": "cluster_handler_0x0000",
                "endpoint_id": 1,
                "cluster": {
                  "id": 0,
                  "name": "Basic",
                  "type": "server"
                },
                "id": "1:0x0000",
                "unique_id": "**REDACTED**",
                "status": "INITIALIZED",
                "value_attribute": null
              }
            ],
            "device_ieee": "**REDACTED**",
            "endpoint_id": 1,
            "available": true,
            "group_id": null,
            "attribute": null,
            "suggested_display_precision": null,
            "divisor": 1,
            "multiplier": 1,
            "unit": null
          },
          "state": {
            "class_name": "LQISensor",
            "available": true,
            "state": 96
          }
        },
        {
          "info_object": {
            "fallback_name": null,
            "unique_id": "**REDACTED**",
            "migrate_unique_ids": [],
            "platform": "sensor",
            "class_name": "RSSISensor",
            "translation_key": "rssi",
            "device_class": "signal_strength",
            "state_class": "measurement",
            "entity_category": "diagnostic",
            "entity_registry_enabled_default": false,
            "enabled": true,
            "primary": false,
            "cluster_handlers": [
              {
                "class_name": "BasicClusterHandler",
                "generic_id": "cluster_handler_0x0000",
                "endpoint_id": 1,
                "cluster": {
                  "id": 0,
                  "name": "Basic",
                  "type": "server"
                },
                "id": "1:0x0000",
                "unique_id": "**REDACTED**",
                "status": "INITIALIZED",
                "value_attribute": null
              }
            ],
            "device_ieee": "**REDACTED**",
            "endpoint_id": 1,
            "available": true,
            "group_id": null,
            "attribute": null,
            "suggested_display_precision": null,
            "divisor": 1,
            "multiplier": 1,
            "unit": "dBm"
          },
          "state": {
            "class_name": "RSSISensor",
            "available": true,
            "state": -76
          }
        },
        {
          "info_object": {
            "fallback_name": null,
            "unique_id": "**REDACTED**",
            "migrate_unique_ids": [],
            "platform": "sensor",
            "class_name": "Battery",
            "translation_key": null,
            "device_class": "battery",
            "state_class": "measurement",
            "entity_category": "diagnostic",
            "entity_registry_enabled_default": true,
            "enabled": true,
            "primary": false,
            "cluster_handlers": [
              {
                "class_name": "PowerConfigurationClusterHandler",
                "generic_id": "cluster_handler_0x0001",
                "endpoint_id": 1,
                "cluster": {
                  "id": 1,
                  "name": "Power Configuration",
                  "type": "server"
                },
                "id": "1:0x0001",
                "unique_id": "**REDACTED**",
                "status": "INITIALIZED",
                "value_attribute": "battery_voltage"
              }
            ],
            "device_ieee": "**REDACTED**",
            "endpoint_id": 1,
            "available": true,
            "group_id": null,
            "attribute": "battery_percentage_remaining",
            "suggested_display_precision": 0,
            "divisor": 1,
            "multiplier": 1,
            "unit": "%"
          },
          "state": {
            "class_name": "Battery",
            "available": true,
            "state": 100.0,
            "battery_voltage": 3.0
          }
        },
        {
          "info_object": {
            "fallback_name": null,
            "unique_id": "**REDACTED**",
            "migrate_unique_ids": [],
            "platform": "sensor",
            "class_name": "Temperature",
            "translation_key": null,
            "device_class": "temperature",
            "state_class": "measurement",
            "entity_category": null,
            "entity_registry_enabled_default": true,
            "enabled": true,
            "primary": false,
            "cluster_handlers": [
              {
                "class_name": "TemperatureMeasurementClusterHandler",
                "generic_id": "cluster_handler_0x0402",
                "endpoint_id": 1,
                "cluster": {
                  "id": 1026,
                  "name": "Temperature Measurement",
                  "type": "server"
                },
                "id": "1:0x0402",
                "unique_id": "**REDACTED**",
                "status": "INITIALIZED",
                "value_attribute": "measured_value"
              }
            ],
            "device_ieee": "**REDACTED**",
            "endpoint_id": 1,
            "available": true,
            "group_id": null,
            "attribute": "measured_value",
            "suggested_display_precision": null,
            "divisor": 100,
            "multiplier": 1,
            "unit": "\u00b0C"
          },
          "state": {
            "class_name": "Temperature",
            "available": true,
            "state": 22.1
          }
        },
        {
          "info_object": {
            "fallback_name": null,
            "unique_id": "**REDACTED**",
            "migrate_unique_ids": [],
            "platform": "sensor",
            "class_name": "Humidity",
            "translation_key": null,
            "device_class": "humidity",
            "state_class": "measurement",
            "entity_category": null,
            "entity_registry_enabled_default": true,
            "enabled": true,
            "primary": false,
            "cluster_handlers": [
              {
                "class_name": "RelativeHumidityClusterHandler",
                "generic_id": "cluster_handler_0x0405",
                "endpoint_id": 1,
                "cluster": {
                  "id": 1029,
                  "name": "Relative Humidity Measurement",
                  "type": "server"
                },
                "id": "1:0x0405",
                "unique_id": "**REDACTED**",
                "status": "INITIALIZED",
                "value_attribute": "measured_value"
              }
            ],
            "device_ieee": "**REDACTED**",
            "endpoint_id": 1,
            "available": true,
            "group_id": null,
            "attribute": "measured_value",
            "suggested_display_precision": null,
            "divisor": 100,
            "multiplier": 1,
            "unit": "%"
          },
          "state": {
            "class_name": "Humidity",
            "available": true,
            "state": 3.0
          }
        }
      ]
    },
    "neighbors": [],
    "routes": []
  }
}

Logs

Logs

[Paste the logs here]

Custom quirk

Custom quirk

[Paste your custom quirk here]

Additional information

No response

roggy85 avatar Jun 11 '25 12:06 roggy85

I have the same problem. The device shows up in ZHA, but it only reports 0% humidity.

TS0601 by _TZE200_wqashyqo

marioschulz93 avatar Jun 13 '25 04:06 marioschulz93

I am using this with z2m but same issue. Resetting and reconnecting the sensor several times seems to get it eventually sending also soil moisture data. I have several and I am trying to find reliable way to get these added to system. Something in the initialization is funky, probably because tuya.

Image Image

whcrg avatar Jun 16 '25 09:06 whcrg

heya, I ordered 3 soil moisture sensors which are of same "model" but different ID (_TZE200_npj9bug3).

My PCB looks different than yours but still shares the ZG-303Z label.

Image

Image

So why I am derailing this issue here? Because I assume the issue with your sensor and mine here - is similar.

the "moisture" value you see - is not a "soil moisture" but a "water level" value. I placed the sensor in a glass and depending on the water level in the glass, I received different "moisture" values. So "out of the glass" = 0%, almost fully submerged (till "lowest line" indicator) displayed 87% to me.

I have bitten into the sour apple and plugged in a freshly received "tuya multi mode gateway" (thanks "ali coins" ... only 6 euros :)), registered at tuya and checked what happens there ... the sensor displays 3 values: temperature, humidity - and moisture.

Image

submerging it more...

Image

I checked the IDs they use in "tuya" and tried to expose the stuff via quirks to HomeAssistant ... but yeah, it somehow does not work the way I thought (am new to HA -- using it for a week now, only fixed some zha-python issues I encountered with python 3.13 in an ubuntu LXC container ... different story).

I tried to use only portions of the "below" code but I was not able to extract the "humidity" value (the 60% as seen above). It always stayed "0". The code below uses values I extracted from the settings screen in tuya (calibration limits, sampling intervals ...). As seen on the image above the sensor is a "WHT20" - which is a temperature and (air) humidity sensor (this is why it is placed right next to a small hole in the plastic housing of the sensor). So the humidity is not a wrongly labeled "soil moisture" but real air humidity.

#   "3":"Moisture",
#   "5":"Air temperature",
#   "9":"Temp unit convert",
#   "15":"Battery level",
#   "101":"Air temperature",
#   "102":"Soil Humidity Calibration",
#   "104":"Air Temp Calibration",
#   "105":"Air Humidity Calibration",
#   "106":"Water shortage",
#   "109":"Current Humidity",
#   "110":"Water shortage warning",
#   "111":"Air Humidity Sampling",
#   "112":"Soil moisture sampling"
(
    TuyaQuirkBuilder("_TZE200_npj9bug3", "TS0601")
    .tuya_temperature(dp_id=5)
    .tuya_battery(dp_id=15)
    .tuya_soil_moisture(dp_id=3)
    .tuya_number(
        dp_id=109,
        attribute_name="current_humidity",
        type=t.uint16_t,
        unit=PERCENTAGE,
        min_value=0,
        max_value=100,
        step=1,
        entity_type=EntityType.STANDARD,
        translation_key="current_humidity",
        fallback_name="current_humidity",
    )
    .tuya_enum(
        dp_id=9,
        attribute_name="display_unit",
        enum_class=TuyaTempUnitConvert,
        entity_type=EntityType.CONFIG,
        translation_key="display_unit",
        fallback_name="Display unit",
    )
    .tuya_number(
        dp_id=102,
        attribute_name="soil_humidity_calibration",
        type=t.uint16_t,
        unit=PERCENTAGE,
        min_value=-30,
        max_value=30,
        step=1,
        entity_type=EntityType.CONFIG,
        translation_key="soil_humidity_calibration",
        fallback_name="soil_humidity_calibration",
    )
    .tuya_number(
        dp_id=104,
        attribute_name="air_temp_calibration",
        type=t.int32s,
        unit=UnitOfTemperature.CELSIUS,
        min_value=-2.0,
        max_value=2.0,
        step=0.1,
        entity_type=EntityType.CONFIG,
        translation_key="air_temp_calibration",
        fallback_name="air_temp_calibration",
    )
    .tuya_number(
        dp_id=105,
        attribute_name="air_humidity_calibration",
        type=t.uint16_t,
        unit=PERCENTAGE,
        min_value=-20,
        max_value=20,
        step=1,
        entity_type=EntityType.CONFIG,
        translation_key="air_humidity_calibration",
        fallback_name="air_humidity_calibration",
    )
    .tuya_number(
        dp_id=111,
        attribute_name="air_humidity_sampling",
        type=t.uint16_t,
        device_class=SensorDeviceClass.DURATION,
        unit=UnitOfTime.SECONDS,
        min_value=5,
        max_value=360,
        step=1,
        entity_type=EntityType.CONFIG,
        translation_key="air_humidity_sampling",
        fallback_name="air_humidity_sampling",
    )
    .tuya_number(
        dp_id=112,
        attribute_name="soil_moisture_sampling",
        type=t.uint16_t,
        device_class=SensorDeviceClass.DURATION,
        unit=UnitOfTime.SECONDS,
        min_value=5,
        max_value=360,
        step=1,
        entity_type=EntityType.CONFIG,
        translation_key="soil_moisture_sampling",
        fallback_name="soil_moisture_sampling",
    )
    .skip_configuration()
    .add_to_registry()
)

Dunno if that is of help for others?

@TheJulianJES - I have read your name in various issues when I checked the cheap "motion sensors" so maybe you can chime in and explain if eg it is needed to write configs first to a sensor to make it properly work.

In tuya I have a message box in the settings, telling me I need to tap the "wake up button" once on the device when adjusting settings (so the button waking it up - or on long press forcing a "re-pair"). So when adjusting calibration values I set the value in tuya to eg "+ 1.0" and it snaps back to "0.0". Tapping the sensor wake up button ... and the value refreshes to "1.0". Iot-Dev-Logs then also contain the entry, that the calibration value was changed.

Edit: The PCB seems to contain some UART-connectors, dunno if it would help to check what "happens there".

Edit2: I ordered 3 of them - and ONE seems to kind of "report" something:

Image

You identify when I gave some water to the soil ... and the "step down" at the end is when I moved the sensor out of the soil a tiny bit (to check if the "water level" thingy somehow is used to calculated values ... yes it has an effect - so values can only be compared if sensor is not moved, soil is not "shrinking" during drying...)

Edit3:

CNTOP ZG_ZIGBEE_SDK V1.2
Flash error:60

happens when connecting VCC [the not-round hole],GND,?,RX,TX

Despite that, no "useful" output (I am surely doing it wrong)

GWRon avatar Jun 18 '25 07:06 GWRon

@roggy85 I asked the vendor for help with the sensor ... and while they of course misunderstood me as usual ("place sensor less far away from the hub") they have sent me a download link with a "cntop_zigbee_sensor.js" and a pdf explaining how to add stuff to homeassistant/zigbee2mqtt (I never mentioned that - explicitely used "tuya" only for them - so they cannot blame me being the culprit :p).

Anyways - this js file contained THIS for YOUR device:

  {
    fingerprint: tuya.fingerprint("TS0601", ["_TZE200_wqashyqo"]),
    model: "ZG-303Z",
    vendor: "HOBEIAN",
    description: "Soil moisture sensor",
    fromZigbee: [tuya.fz.datapoints],
    toZigbee: [tuya.tz.datapoints],
    configure: tuya.configureMagicPacket,
    exposes: [
      e
        .enum("water_warning", ea.STATE, ["none", "alarm"])
        .withDescription("Water shortage warning"),
      e.temperature(),
      e.humidity(),
      e
        .numeric("soil_humidity", ea.STATE)
        .withUnit("%")
        .withDescription("Soil relative humidity"),
      tuya.exposes.temperatureUnit(),
      tuya.exposes.temperatureCalibration(),
      tuya.exposes.humidityCalibration(),
      e
        .numeric("soil_calibration", ea.STATE_SET)
        .withValueMin(-30)
        .withValueMax(30)
        .withValueStep(1)
        .withUnit("%")
        .withDescription("Soil Humidity calibration"),
      e
        .numeric("air_sampling", ea.STATE_SET)
        .withValueMin(5)
        .withValueMax(3600)
        .withValueStep(1)
        .withUnit("s")
        .withDescription("Air temperature and humidity sampling"),
      e
        .numeric("soil_sampling", ea.STATE_SET)
        .withValueMin(5)
        .withValueMax(3600)
        .withValueStep(1)
        .withUnit("s")
        .withDescription("Soil humidity sampling"),
      e
        .numeric("soil_humidity_warning", ea.STATE_SET)
        .withValueMin(0)
        .withValueMax(100)
        .withValueStep(1)
        .withUnit("%")
        .withDescription("Soil water shortage humidity value"),
      e.battery(),
    ],
    meta: {
      tuyaDatapoints: [
        [
          1,
          "water_warning",
          tuya.valueConverterBasic.lookup({
            none: tuya.enum(0),
            alarm: tuya.enum(1),
          }),
        ],
        [103, "temperature", tuya.valueConverter.divideBy10],
        [109, "humidity", tuya.valueConverter.raw],
        [107, "soil_humidity", tuya.valueConverter.raw],
        [108, "battery", tuya.valueConverter.raw],
        [106, "temperature_unit", tuya.valueConverter.temperatureUnit],
        [104, "temperature_calibration", tuya.valueConverter.divideBy10],
        [105, "humidity_calibration", tuya.valueConverter.raw],
        [102, "soil_calibration", tuya.valueConverter.raw],
        [111, "air_sampling", tuya.valueConverter.raw],
        [112, "soil_sampling", tuya.valueConverter.raw],
        [110, "soil_humidity_warning", tuya.valueConverter.raw],
      ],
    },
  },

(which is similar to what I made up in python). This won't help with the reported moisture value at all - it simply uses what it receives, there is no "calculation" placed somewhere.

GWRon avatar Jun 19 '25 08:06 GWRon

@GWRon I have the same device, _TZE200_npj9bug3. The code to handle it is already merged in master branch : https://github.com/Koenkk/zigbee-herdsman-converters/pull/9430/files#diff-0157bb612c63e92c50f93116e1f50bf8e263030ed26d86caa28479159c2a9f4a (around line 17830)

BUT : I tried this version, through docker with latest-dev container, and it does not work. I only have temp and humidity, and sometimes soil moisture, 0 , or 70. You said it was detected as a level sensor, but this make sense to me, as it is a capacity sensor, it can detect water level, or soil mosture, as value is calculated from water in both case. Or maybe I didn't understood what you said.

I also tried your code version, I only have humidity.

I don't know if my device is faultly or if it is a software issue

theccm avatar Jun 24 '25 06:06 theccm

I don't know how the moisture is detected and what formulas are applied to the raw sensor data/values. If it is capacity based then yes it should be possible to use it for a kind-of-moisture calculation. I am just unsure why the values are almost steadily "same height" for days here - with the moisture of the soil being "feelable" more dry each further day.

Image

And nope ... I did not drown the plant - only a "sip" of water into a relatively big pot :)

This is a different plant - 2m next to the other, same "device type" but absolutely different "value noise" and less measurement time stamps.

Image

The "first" one was always more jittery and felt kind of "measuring something" while the others looked like exposing some too much averaged values (which then indicates they should be kinda-configured in tuya in advanced so that eg sampling is done differently -- which then can be something needed to tackled in the configs here in the quirk file).

Regarding your "0 or 70" values: When I tried in the glass of water, I also had values between 0 and 70 (so placing it halfly into water for 50 etc). Is yours kind of binary? so 0 or 70 but nothing inbetween? If so, then yes, something might be faulty or a different sensor value is interpreted (the soil moisture warning on/off switch or so).

Edit: Regarding air humidity -- as stated, in tuya there is a value displayed, so the sensor was delivering useful values - but my config did not make it available inside of homeassistant/zha - I did not have time to investigate more on how to debug such stuff (eg retrieving the raw data the sensor spits out...)

GWRon avatar Jun 24 '25 06:06 GWRon

Hi, just wanted to add to this thread with findings from a closely related sensor.

I’ve been debugging the Tuya soil sensor _TZE200_npj9bug3 (also TS0601), which looks physically identical to the one in this thread: _TZE200_wqashyqo.

I posted a full ZHA support request in issue #4144, and wanted to confirm that I temporarily tested my sensor in Zigbee2MQTT, and it reports air temperature, air humidity, and soil moisture as separate values.

Here’s what I received in Z2M:

json Copy Edit { "temperature": 29.7, "humidity": 58, "soil_moisture": 78, "battery": 100 } Based on the logs, the data point mappings appear to match what’s already mentioned here:

3: soil moisture

5: air temperature

109: air humidity

In ZHA, however, the soil moisture is misclassified as humidity, and the actual air humidity sensor isn’t exposed at all. So the device needs a custom quirk that maps the Tuya DP values properly and sets the correct device_class for each one.

Hopefully this info helps confirm the pattern across models. Happy to help test if needed!

Arashrk82 avatar Jun 24 '25 17:06 Arashrk82

@GWRon You may want to test the quirk in #4160.

makuser avatar Jul 02 '25 23:07 makuser

@roggy85 You also may want to test the quirk in #4161, as I do not have that specific device to test myself. Please let us know if it works.

makuser avatar Jul 03 '25 00:07 makuser

@GWRon You may want to test the quirk in #4160.

@makuser I applied your quirk - it adds the calibration elements, exposes air humidity and soil moisture.

Please excuse the now incoming wall of text and images - trying to help as much as possible.

But I cannot adjust the values (eg humidity calibration) without a toast message (and it is ignored for humidity but kinda does something for soil moisture): Image temperature offset adjustment (-2 to +2) has no effect, displayed and "received" value stay the same).

I have to add that the air humidity is off by 15-20 Kelvin - generically/also before your quirk - regarding a normal temperature/air humidity sensor. Guess it is adding parts of the transpiring soil moisture because of the sensor position right over the soil). BUT: there is something "strange" happening there. I only see this value for a short moment. As soon as soil moisture "refreshes" separately, it kind of "overrides" the air humidity value and then both are the same (both have the same value then).

What I mean is: I press the "wake up button" - it then sends new sensor values. Both, humidty and moisture, refresh. They show different values then (in this very case air humidity is still too high, but at least values here differed by some "percents" (absolute values)). Soon after this the "moisture" value refreshes again - and then BOTH show the same value. It is not always repeatable and then values stay the same. Guess this is a flaw in the firmware of the device.

Image

Image

Image

So I tested something more: I simply pulled out the sensor (and cleaned the "stripe" as it else detects moisture, and woke it up:

Image Now both (moisture and humidity) show 0% ...means humidity is linked to moisture now.

On next value update humidity refreshes - this value is only shown for ... 1-2 seconds -- then it switches back to "0%" (so the value of the soil moisture) Image

So this looks again as if the soil moisture value "overrides" the humidity value right? But looking at "last refresh" time of the individual sensors I only see the humidity one being updates "x seconds ago" while the moisture says "2 minutes ago". Which looks more as if multiple incoming data values are interpreted as "humidity" while only the "first" one is the the actual value. Pressing "wake up" it again syncs the two displayed values to the same value.

After I played with the "calibration" values (trying to adjust things -- see top of my reply) it now seems to no longer be "linked together". Sounds good? Nope, now a "wake up" does not refresh the value of the air humidity anymore while soil moisture correctly displays "x seconds ago" then (and humidity says "x minutes ago"). Dunno if the calibration stuff has send odd values during int-int16 casting and then submitting, something which broke the "firmware logic". I removed battery and placed it in again. Now it reports 96% air humidity. Maybe calibration stuff is persisting or reapplied right on start, dunno. The value itself is not constant, it decreases for the moment (88%, 83%) - it just "auto updates" but ignores "wake up" data emission for this specific sensor value (or the data is there but not extracted correctly from ZHA/quirks).

So what we got with your quirk now:

  • a flipped (but despite that) working "dry/wet" binary value
  • soil moisture (so correctly labelled)
  • working soil moisture calibration (it requires some seconds to take effect - because of the "sampling time")

Other calibration "might" work but require a lot of time to be applied by the device, dunno - but for now it seems as if at leas air humidity calibration is not taken into consideration (else I would be able to move from "96%" to "66%")


Anyways. There is one smaller flaw left in your commit: the "moisture" label is switched. It shows "dry" if you have a lot of water in the soil, and "wet" if you eg pull out the sensor out of the soil. So dry/wet seems to need a binary flip (x = 1 - x).

In the plant pot: Image

Pulled out: Image

GWRon avatar Jul 03 '25 06:07 GWRon

@roggy85 You also may want to test the quirk in #4161, as I do not have that specific device to test myself. Please let us know if it works.

Hi, thanks for that commit, I just added the quirk and I now get readings for the "Soil Humidity" and "Moisture"

Whats a bit irritating is, that it seems to have somewhat the same behavior as @GWRon . Sometimes it has different readings for Humidity and Soil Humidity - sometimes it just seems to have the exact same readings - which is strange.

I did not played with any of the Calibrations and I wanted to watch for a longer Time. Currently I have two sensors in place.

Image

Image

So its a bit strange what the readings are showing - and I just watered both plants and I don't see an increase in the moisture - but I don't know how long it would take to send the update (and the last update is already 27 minutes old - but the humidity was updated 2 minutes ago...)

roggy85 avatar Jul 03 '25 18:07 roggy85

While this is waiting to be merged, how can I take the patch to tuya_sensor.py and turn it into a ZHA quirk using the signature & replacement hashes?

dtaylor avatar Jul 22 '25 00:07 dtaylor

The quirk in https://github.com/zigpy/zha-device-handlers/pull/4161 works for me too with the same behaviour mentioned by @GWRon.

I also noticed that the soil moisture/ humidity seem to correlate with the water level and not the soil moisture as it should be. The sensor seems to be able to measure soil moisture, water level and temperature. Has anybody observed the same and is there any fix for that?

patafix18 avatar Jul 22 '25 19:07 patafix18

I also noticed that the soil moisture/ humidity seem to correlate with the water level and not the soil moisture as it should be. The sensor seems to be able to measure soil moisture, water level and temperature. Has anybody observed the same and is there any fix for that?

What do you mean by "water level" in the context of soil measuring? Soil is usually not in liquid form as water and soil humidity means percentage bound water contents in said area of soil and soil is soking up the water, distributing it. If you have a plant pot without hole at the bottom and fill the soil pot with so much water, that it actually has a water level in there, you're most likely killing the plant anyway 😜

If you mean that you tested the depth of immersion of the sensor in water, which then shows a "soil humidity" correlating to the depth in the water, then that's basically just how this capacitor sensor works. It's all the same if the water is not measured from the bottom up, but somewhere in between, or bottom down, or any combination of amount of droplets all over the surface of the probe. Since usually water is more or less evenly distributed in soil, the soil humidity at the top of this short sensor should more or less be equal to the bottom part of the sensor, giving an averaged value of the length of the probe.

Otherwise, not much any software fix on the receiving zigbee end could change about that electrical behavior.

makuser avatar Jul 22 '25 20:07 makuser

While this is waiting to be merged, how can I take the patch to tuya_sensor.py and turn it into a ZHA quirk using the signature & replacement hashes?

@dtaylor Well you can more or less take the added lines in #4160 or #4161, according to your device, but then also some other imports that already exist in that file and put it into a standalone quirk file.

For these two almost identical devices, take this as an example and fill it with the rest of the code from either PR.

In my zha_quirks/zg_303z.py I have something like this:

from zigpy.quirks.v2.homeassistant import (
    EntityType,
    PERCENTAGE,
    UnitOfTemperature,
    UnitOfTime,
)
from zigpy.quirks.v2.homeassistant.binary_sensor import BinarySensorDeviceClass
from zigpy.quirks.v2.homeassistant.number import NumberDeviceClass

import zigpy.types as t

from zhaquirks.const import BatterySize
from zhaquirks.tuya.builder import TuyaQuirkBuilder
from zhaquirks.tuya.tuya_sensor import TuyaTempUnitConvert

# start here
(
    TuyaQuirkBuilder("HOBEIAN", "ZG-303Z")
    .applies_to("_TZE200_npj9bug3", "TS0601")
    .tuya_soil_moisture(dp_id=3)
# [... take the class code from the PR of your choice and fill this example with it ...]
    .add_to_registry()
)
# end here

makuser avatar Jul 22 '25 20:07 makuser

@makuser Thank you! I almost had it (still new to HA) but after I added the quirk ZHA picked it up automatically. The humidity & soil moisture are sometimes the same as others reported, but not always. This is better than before where I didn't get soil moisture at all.

dtaylor avatar Jul 23 '25 23:07 dtaylor

@makuser I used #4160 using the Hobeian ZG-303Z.

There is a bug: In general Air moisture and Soil moisture work and deliver comprehensible data. But whenever soil moisture changes value, it overwrites the air moisture. Air moisture then changes back to the correct value, when it is updated with a new value. It seems to be that soil moisture is mapped to the 2 data points soil and air moisture, whereas air moisture works as intended.

Image

blue line air moisture, yello line soil moisture

commandermatz avatar Aug 02 '25 11:08 commandermatz

@makuser Since I am no IT-guy and don't have the needed skillset, I asked Gemini for assistance. I'm sure, you will find the bug way faster than I can:

Image Image

So far I struggle to change the file, when doing, the process of connecting the sensor to ZHA fails.

commandermatz avatar Aug 02 '25 11:08 commandermatz

@makuser I used #4160 using the Hobeian ZG-303Z.

There is a bug: In general Air moisture and Soil moisture work and deliver comprehensible data. But whenever soil moisture changes value, it overwrites the air moisture. Air moisture then changes back to the correct value, when it is updated with a new value. It seems to be that soil moisture is mapped to the 2 data points soil and air moisture, whereas air moisture works as intended.

Image

blue line air moisture, yello line soil moisture

I had the same impression. But I didn't understand if it was a bug on the hardware side or on the homeassistant side?

Machalkas avatar Aug 04 '25 08:08 Machalkas

@makuser I used #4160 using the Hobeian ZG-303Z. There is a bug: In general Air moisture and Soil moisture work and deliver comprehensible data. But whenever soil moisture changes value, it overwrites the air moisture. Air moisture then changes back to the correct value, when it is updated with a new value. It seems to be that soil moisture is mapped to the 2 data points soil and air moisture, whereas air moisture works as intended. Image blue line air moisture, yello line soil moisture

I had the same impression. But I didn't understand if it was a bug on the hardware side or on the homeassistant side?

I guess it's on the zigbee-quirk side. In Z2M it seems to work from what I read. I started with ZHA and try to avoid changing to Z2M with my 60ish devices and hundreds of entities I would have to synch again...

commandermatz avatar Aug 04 '25 08:08 commandermatz

Does anyone else have the issue that the soil moisture value doesn’t update automatically? On my devices, it only updates when I press the button, while all other sensor values update correctly.

patafix18 avatar Aug 08 '25 20:08 patafix18

@patafix18 Yes, I have the same issue.

moritzruth avatar Aug 19 '25 20:08 moritzruth

@roggy85 You also may want to test the quirk in #4161, as I do not have that specific device to test myself. Please let us know if it works.

@makuser I also would like to test this quirk since I also have this issue with my HOBEIAN ZG-303Z

How does that work to install a quirk? Never did that before.

denniepinto avatar Aug 27 '25 15:08 denniepinto

https://github.com/zigpy/zha-device-handlers/pull/4160#issuecomment-3157916772

danielholm avatar Aug 27 '25 17:08 danielholm

Does anyone else have the issue that the soil moisture value doesn’t update automatically? On my devices, it only updates when I press the button, while all other sensor values update correctly.

Yeah, same issue.

I came looking for a solution to it not displaying the moisture value originally, and came across this PR and added the quirks file, which has solved that problem.

Did you ever find a solution?

michaelpallister avatar Oct 12 '25 21:10 michaelpallister

Has anyone succeeded running this successfully with a board that has has ZSSF01 on it? It's a variant with a illuminance sensor. I have the following issue: https://github.com/Koenkk/zigbee2mqtt/issues/29254 but I think it's the device problem, so I am curious if anyone got it working on ZHA. This is how the board looks:

Image

hnykda avatar Oct 26 '25 20:10 hnykda

I tried the quirk in #4160 and got the same override issue. I now found a solution for that (see the code I used below) There is only one problem with that. Now, ZHA thinks that there are two humidity sensors (one is the real one, the other just the moisture sensor). But now moisture and humidity don't override each other. To fix the "issue" I just deactivated the fake humidity sensor.

Edit: the new code inverts the water_warning logic, so now the sensor displays wet when it's wet and dry when it's dry.

"""Tuya temp and humidity sensors."""

import copy

from zigpy.quirks.v2 import EntityPlatform, EntityType
from zigpy.quirks.v2.homeassistant import PERCENTAGE, UnitOfTemperature, UnitOfTime
from zigpy.quirks.v2.homeassistant.binary_sensor import BinarySensorDeviceClass
from zigpy.quirks.v2.homeassistant.number import NumberDeviceClass
from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass
import zigpy.types as t
from zigpy.zcl import foundation

from zhaquirks.const import BatterySize
from zhaquirks.tuya import (
    TUYA_SET_TIME,
    TuyaPowerConfigurationCluster2AAA,
    TuyaTimePayload,
)
from zhaquirks.tuya.builder import TuyaQuirkBuilder
from zhaquirks.tuya.mcu import TuyaMCUCluster


class TuyaTempUnitConvert(t.enum8):
    """Tuya temperature unit convert enum."""

    Celsius = 0x00
    Fahrenheit = 0x01


class TuyaNousTempHumiAlarm(t.enum8):
    """Tuya temperature and humidity alarm enum."""

    LowerAlarm = 0x00
    UpperAlarm = 0x01
    Canceled = 0x02



(
    TuyaQuirkBuilder("HOBEIAN", "ZG-303Z")
    .applies_to("_TZE200_npj9bug3", "TS0601")  # COOLO CS-201Z
    .tuya_temperature(dp_id=5, scale=10)
    .tuya_enum(
        dp_id=9,
        attribute_name="display_unit",
        enum_class=TuyaTempUnitConvert,
        entity_type=EntityType.CONFIG,
        translation_key="display_unit",
        fallback_name="Display unit",
    )
    
    .tuya_sensor(
        dp_id=3,
        type=t.uint16_t,
        attribute_name="soil_moisture",
        unit=PERCENTAGE,
        device_class=SensorDeviceClass.MOISTURE,
        entity_type=EntityType.STANDARD,
        translation_key="soil_moisture",
        fallback_name="Soil moisture",
    )


    .tuya_battery(dp_id=15, battery_type=BatterySize.AAA)
    .tuya_number(
        dp_id=102,
        attribute_name="soil_moisture_calibration",
        type=t.uint16_t,
        unit=PERCENTAGE,
        min_value=-30,
        max_value=30,
        step=1,
        entity_type=EntityType.CONFIG,
        device_class=NumberDeviceClass.HUMIDITY,
        translation_key="soil_moisture_calibration",
        fallback_name="Soil moisture Calibration",
    )
    .tuya_number(
        dp_id=104,
        attribute_name="temperature_calibration",
        type=t.uint16_t,
        unit=UnitOfTemperature.KELVIN,
        min_value=-2,
        max_value=2,
        step=0.1,
        multiplier=10,
        entity_type=EntityType.CONFIG,
        device_class=NumberDeviceClass.TEMPERATURE,
        translation_key="temperature_calibration",
        fallback_name="Temperature Calibration",
    )
    .tuya_number(
        dp_id=105,
        attribute_name="humidity_calibration",
        type=t.uint16_t,
        unit=PERCENTAGE,
        min_value=-30,
        max_value=30,
        step=1,
        entity_type=EntityType.CONFIG,
        device_class=NumberDeviceClass.HUMIDITY,
        translation_key="humidity_calibration",
        fallback_name="Humidity Calibration",
    )
    # EDITED
    .tuya_dp_attribute(
        dp_id=106,
        attribute_name="dry",
        converter=lambda x: not bool(x),  # invert the logic: 0 -> True, 1 -> False
    )
    .binary_sensor(
        attribute_name="dry",
        cluster_id=TuyaMCUCluster.cluster_id,
        endpoint_id=1,
        entity_type=EntityType.STANDARD,
        device_class=BinarySensorDeviceClass.MOISTURE,
        initially_disabled=False,
        attribute_initialized_from_cache=True,
        translation_key="dry",
        fallback_name="dry",
    )

    .tuya_sensor(
        dp_id=109,
        type=t.uint16_t,
        attribute_name="humidity_value",        
        unit=PERCENTAGE,
        device_class=SensorDeviceClass.HUMIDITY,
        entity_type=EntityType.STANDARD,
        translation_key="humidity",
        fallback_name="Humidity",
    )  
    .tuya_number(
        dp_id=110,
        attribute_name="alarm_soil_moisture_min",
        type=t.uint16_t,
        unit=PERCENTAGE,
        min_value=0,
        max_value=100,
        step=1,
        entity_type=EntityType.CONFIG,
        device_class=NumberDeviceClass.MOISTURE,
        translation_key="alarm_soil_moisture_min",
        fallback_name="Alarm soil moisture min",
    )
    .tuya_number(
        dp_id=111,
        attribute_name="temperature_sampling",
        type=t.uint16_t,
        unit=UnitOfTime.SECONDS,
        min_value=5,
        max_value=3600,
        step=1,
        entity_type=EntityType.CONFIG,
        device_class=NumberDeviceClass.DURATION,
        translation_key="temperature_sampling",
        fallback_name="Temperature Sampling",
    )
    .tuya_number(
        dp_id=112,
        attribute_name="soil_moisture_sampling",
        type=t.uint16_t,
        unit=UnitOfTime.SECONDS,
        min_value=5,
        max_value=3600,
        step=1,
        entity_type=EntityType.CONFIG,
        device_class=NumberDeviceClass.DURATION,
        translation_key="soil_moisture_sampling",
        fallback_name="Soil Moisture Sampling",
    )
    .tuya_enchantment(data_query_spell=True)
    .skip_configuration()
    .add_to_registry()
)
Image

JustReit avatar Nov 07 '25 13:11 JustReit