diff --git a/drivers/SmartThings/matter-sensor/fingerprints.yml b/drivers/SmartThings/matter-sensor/fingerprints.yml index 75d091db8e..3477c7f67a 100644 --- a/drivers/SmartThings/matter-sensor/fingerprints.yml +++ b/drivers/SmartThings/matter-sensor/fingerprints.yml @@ -75,6 +75,12 @@ matterManufacturer: vendorId: 0x142B productId: 0x0002 deviceProfileName: motion-illuminance-temperature-humidity + # Tuya + - id: "4701/49" + deviceLabel: Tuya Gas Sensor + vendorId: 0x125D + productId: 0x0031 + deviceProfileName: tuya-gas matterGeneric: - id: "matter/contact/sensor" deviceLabel: Matter Contact Sensor diff --git a/drivers/SmartThings/matter-sensor/profiles/tuya-gas.yml b/drivers/SmartThings/matter-sensor/profiles/tuya-gas.yml new file mode 100644 index 0000000000..bc4860c56a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/tuya-gas.yml @@ -0,0 +1,12 @@ +name: tuya-gas +components: +- id: main + capabilities: + - id: gasDetector + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SmokeDetector \ No newline at end of file diff --git a/drivers/SmartThings/matter-sensor/src/smoke-co-alarm/init.lua b/drivers/SmartThings/matter-sensor/src/smoke-co-alarm/init.lua index 015cb88be2..9f11526408 100644 --- a/drivers/SmartThings/matter-sensor/src/smoke-co-alarm/init.lua +++ b/drivers/SmartThings/matter-sensor/src/smoke-co-alarm/init.lua @@ -248,6 +248,9 @@ local matter_smoke_co_alarm_handler = { clusters.SmokeCoAlarm.attributes.BatteryAlert, } }, + sub_drivers = { + require("tuya-smoke-co-alarm") + }, can_handle = is_matter_smoke_co_alarm } diff --git a/drivers/SmartThings/matter-sensor/src/test/test_tuya_smoke_co_alarm.lua b/drivers/SmartThings/matter-sensor/src/test/test_tuya_smoke_co_alarm.lua new file mode 100644 index 0000000000..b290cd00d8 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/test/test_tuya_smoke_co_alarm.lua @@ -0,0 +1,118 @@ +-- Copyright 2024 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local test = require "integration_test" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" + +local clusters = require "st.matter.clusters" +clusters.SmokeCoAlarm = require "SmokeCoAlarm" +local version = require "version" +if version.api < 10 then + clusters.SmokeCoAlarm = require "SmokeCoAlarm" + clusters.CarbonMonoxideConcentrationMeasurement = require "CarbonMonoxideConcentrationMeasurement" +end + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("tuya-gas.yml"), + manufacturer_info = { + vendor_id = 0x125D, + product_id = 0x0031, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0016, device_type_revision = 1} -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + {cluster_id = clusters.SmokeCoAlarm.ID, cluster_type = "SERVER", feature_map = clusters.SmokeCoAlarm.types.Feature.CO_ALARM | clusters.SmokeCoAlarm.types.Feature.SMOKE_ALARM}, + }, + device_types = { + {device_type_id = 0x0076, device_type_revision = 1} -- Smoke CO Alarm + } + } + } +}) + +local cluster_subscribe_list = { + clusters.SmokeCoAlarm.attributes.COState, +} + +local function test_init() + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device)) + end + end + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.mock_device.add_test_device(mock_device) + mock_device:expect_metadata_update({ profile = "tuya-gas.yml" }) +end + +test.set_test_init_function(test_init) + +test.register_message_test( + "Test CO state handler", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.SmokeCoAlarm.attributes.COState:build_test_report_data(mock_device, 1, clusters.SmokeCoAlarm.attributes.COState.NORMAL) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.gasDetector.gas.clear()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.SmokeCoAlarm.attributes.COState:build_test_report_data(mock_device, 1, clusters.SmokeCoAlarm.attributes.COState.WARNING) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.gasDetector.gas.detected()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.SmokeCoAlarm.attributes.COState:build_test_report_data(mock_device, 1, clusters.SmokeCoAlarm.attributes.COState.CRITICAL) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.gasDetector.gas.detected()) + } + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-sensor/src/tuya-smoke-co-alarm/init.lua b/drivers/SmartThings/matter-sensor/src/tuya-smoke-co-alarm/init.lua new file mode 100644 index 0000000000..1a82e58a47 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/tuya-smoke-co-alarm/init.lua @@ -0,0 +1,82 @@ +-- Copyright 2024 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" + +local SMOKE_CO_ALARM_DEVICE_TYPE_ID = 0x0076 +local TUYA_CO_MANUFACTURER_ID = 0x125D + +local version = require "version" + +if version.api < 10 then + clusters.SmokeCoAlarm = require "SmokeCoAlarm" +end + +local function is_tuya_smoke_co_alarm(opts, driver, device) + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == SMOKE_CO_ALARM_DEVICE_TYPE_ID and device.manufacturer_info.vendor_id == TUYA_CO_MANUFACTURER_ID then + return true + end + end + end + return false +end + + +local function device_init(driver, device, ib) + device:send( + cluster_base.subscribe(device, ib.endpoint_id, clusters.SmokeCoAlarm.ID, clusters.SmokeCoAlarm.attributes.COState.ID, nil) + ) + -- device:subscribe() +end + +local function info_changed(self, device, event, args) + -- resubscribe to new attributes as needed if a profile switch occured + if device.profile.id ~= args.old_st_store.profile.id then + device:subscribe() + end +end + +-- Matter Handlers -- +local function binary_state_handler_factory(zeroEvent, nonZeroEvent) + return function(driver, device, ib, response) + if ib.data.value == 0 and zeroEvent ~= nil then + device:emit_event_for_endpoint(ib.endpoint_id, zeroEvent) + elseif nonZeroEvent ~= nil then + device:emit_event_for_endpoint(ib.endpoint_id, nonZeroEvent) + end + end +end + +local matter_tuya_smoke_co_alarm_handler = { + NAME = "matter-tyua-smoke-co-alarm", + lifecycle_handlers = { + -- added = device_added, + init = device_init, + infoChanged = info_changed + }, + matter_handlers = { + attr = { + [clusters.SmokeCoAlarm.ID] = { + [clusters.SmokeCoAlarm.attributes.COState.ID] = binary_state_handler_factory(capabilities.gasDetector.gas.clear(), capabilities.gasDetector.gas.detected()), + } + }, + }, + can_handle = is_tuya_smoke_co_alarm +} + +return matter_tuya_smoke_co_alarm_handler \ No newline at end of file