diff --git a/drivers/Unofficial/tuya-zigbee/fingerprints.yml b/drivers/Unofficial/tuya-zigbee/fingerprints.yml index 5c77617d1d..a00a652118 100644 --- a/drivers/Unofficial/tuya-zigbee/fingerprints.yml +++ b/drivers/Unofficial/tuya-zigbee/fingerprints.yml @@ -49,3 +49,8 @@ zigbeeManufacturer: manufacturer: _TZE284_fziifcxj model: TS0601 deviceProfileName: thermostat + - id: _TZ3000_dvagjie2/TS0207 + deviceLabel: Tuya Water Leak Sensor + manufacturer: _TZ3000_dvagjie2 + model: TS0207 + deviceProfileName: water-battery diff --git a/drivers/Unofficial/tuya-zigbee/profiles/water-battery.yml b/drivers/Unofficial/tuya-zigbee/profiles/water-battery.yml new file mode 100644 index 0000000000..ae8929be32 --- /dev/null +++ b/drivers/Unofficial/tuya-zigbee/profiles/water-battery.yml @@ -0,0 +1,14 @@ +name: water-battery +components: +- id: main + capabilities: + - id: waterSensor + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: LeakSensor diff --git a/drivers/Unofficial/tuya-zigbee/src/init.lua b/drivers/Unofficial/tuya-zigbee/src/init.lua index 6313132933..7f73253f79 100644 --- a/drivers/Unofficial/tuya-zigbee/src/init.lua +++ b/drivers/Unofficial/tuya-zigbee/src/init.lua @@ -27,7 +27,8 @@ local unofficial_tuya_driver_template = { require("motion-sensor"), require("smoke-detector"), require("switch"), - require("thermostat") + require("thermostat"), + require("water-leak") }, health_check = false, } diff --git a/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_water_leak.lua b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_water_leak.lua new file mode 100644 index 0000000000..3cf7be17f8 --- /dev/null +++ b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_water_leak.lua @@ -0,0 +1,113 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Mock out globals +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" + +local mock_simple_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("water-battery.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "_TZ3000_dvagjie2", + model = "TS0207", + server_clusters = { clusters.IASZone.ID } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_simple_device) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle added lifecycle event", + function() + test.socket.device_lifecycle:__queue_receive({ mock_simple_device.id, "added" }) + + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message( + "main", + capabilities.waterSensor.water.dry() + ) + ) + + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message( + "main", + capabilities.battery.battery(100) + ) + ) + end, + {} +) + +test.register_message_test( + "Handle IASZone status change (dry)", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_simple_device.id, + clusters.IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx( + mock_simple_device, -- device + 0x00, -- zone_status (dry) + 0x00, -- extended_status + 0x00, -- zone_id + 0x0000 -- delay + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_simple_device:generate_test_message( + "main", + capabilities.waterSensor.water.dry() + ) + } + }, + {} +) + +test.register_message_test( + "Handle IASZone status change (wet)", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_simple_device.id, + clusters.IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx( + mock_simple_device, -- device + 0x01, -- zone_status (wet) + 0x00, -- extended_status + 0x00, -- zone_id + 0x0000 -- delay + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_simple_device:generate_test_message( + "main", + capabilities.waterSensor.water.wet() + ) + } + }, + {} +) + +test.run_registered_tests() diff --git a/drivers/Unofficial/tuya-zigbee/src/water-leak/init.lua b/drivers/Unofficial/tuya-zigbee/src/water-leak/init.lua new file mode 100644 index 0000000000..be63260b4c --- /dev/null +++ b/drivers/Unofficial/tuya-zigbee/src/water-leak/init.lua @@ -0,0 +1,78 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local defaults = require "st.zigbee.defaults" +local clusters = require "st.zigbee.zcl.clusters" + +local FINGERPRINTS = { + { mfr = "_TZ3000_dvagjie2", model = "TS0207"} +} + +local function is_tuya_water_leak(opts, driver, device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true + end + end + return false +end + +local function device_added(self, device) + device:emit_event(capabilities.waterSensor.water.dry()) + device:emit_event(capabilities.battery.battery(100)) +end + +local generate_event_from_zone_status = function(driver, device, zone_status, zb_rx) + local event + + if zone_status:is_alarm1_set() then + event = capabilities.waterSensor.water.wet() + else + event = capabilities.waterSensor.water.dry() + end + + if event ~= nil then + device:emit_event(event) + end +end + +local ias_zone_status_change_handler = function(driver, device, zb_rx) + generate_event_from_zone_status(driver, device, zb_rx.body.zcl_body.zone_status, zb_rx) +end + +local ias_zone_status_attr_handler = function(driver, device, zone_status, zb_rx) + generate_event_from_zone_status(driver, device, zone_status, zb_rx) +end + +local tuya_water_leak_driver = { + NAME = "tuya water leak", + supported_capabilities = { + capabilities.waterSensor, + capabilities.battery, + capabilities.refresh + }, + zigbee_handlers = { + attr = { + [clusters.IASZone.ID] = { + [clusters.IASZone.attributes.ZoneStatus.ID] = ias_zone_status_attr_handler + } + }, + cluster = { + [clusters.IASZone.ID] = { + [clusters.IASZone.client.commands.ZoneStatusChangeNotification.ID] = ias_zone_status_change_handler + } + } + }, + lifecycle_handlers = { + added = device_added + }, + can_handle = is_tuya_water_leak +} + +defaults.register_for_default_handlers( + tuya_water_leak_driver, + tuya_water_leak_driver.supported_capabilities, + {} +) +return tuya_water_leak_driver