From e0e43ac223ddc29ec1f7a110b2d6a16272712a48 Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Tue, 19 Sep 2023 10:43:28 -0500 Subject: [PATCH] Matter-sensor add support for mains powered sensors --- .../matter-sensor/fingerprints.yml | 6 +- .../profiles/matter-motion-battery.yml | 14 ++ .../matter-sensor/profiles/motion-battery.yml | 2 +- .../profiles/motion-illuminance-battery.yml | 16 ++ .../SmartThings/matter-sensor/src/init.lua | 44 ++++- .../test/test_matter_sensor_featuremap.lua | 186 ++++++++++++++++++ 6 files changed, 263 insertions(+), 5 deletions(-) create mode 100644 drivers/SmartThings/matter-sensor/profiles/matter-motion-battery.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/motion-illuminance-battery.yml create mode 100644 drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_featuremap.lua diff --git a/drivers/SmartThings/matter-sensor/fingerprints.yml b/drivers/SmartThings/matter-sensor/fingerprints.yml index a571e96bf9..395baefa25 100644 --- a/drivers/SmartThings/matter-sensor/fingerprints.yml +++ b/drivers/SmartThings/matter-sensor/fingerprints.yml @@ -10,7 +10,7 @@ matterManufacturer: deviceLabel: Eve Motion vendorId: 0x130a productId: 0x0059 - deviceProfileName: matter-motion-battery-illuminance + deviceProfileName: motion-illuminance-battery - id: "Eve/DoorAndWindow" deviceLabel: Eve Door and Window vendorId: 0x130a @@ -49,12 +49,12 @@ matterGeneric: deviceTypes: - id: 0x0107 # Occupancy Sensor - id: 0x0106 # Light Sensor - deviceProfileName: matter-motion-battery-illuminance + deviceProfileName: motion-illuminance-battery - id: "matter/motion-sensor" deviceLabel: Matter Motion Sensor deviceTypes: - id: 0x0107 # Occupancy Sensor - deviceProfileName: matter-motion-battery + deviceProfileName: motion-battery - id: "matter/motion-sensor/contact" deviceLabel: Matter Motion/Contact Sensor deviceTypes: diff --git a/drivers/SmartThings/matter-sensor/profiles/matter-motion-battery.yml b/drivers/SmartThings/matter-sensor/profiles/matter-motion-battery.yml new file mode 100644 index 0000000000..0e2b57c71e --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/matter-motion-battery.yml @@ -0,0 +1,14 @@ +name: matter-motion-battery +components: +- id: main + capabilities: + - id: motionSensor + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: MotionSensor diff --git a/drivers/SmartThings/matter-sensor/profiles/motion-battery.yml b/drivers/SmartThings/matter-sensor/profiles/motion-battery.yml index 0e2b57c71e..0c5639ca06 100644 --- a/drivers/SmartThings/matter-sensor/profiles/motion-battery.yml +++ b/drivers/SmartThings/matter-sensor/profiles/motion-battery.yml @@ -1,4 +1,4 @@ -name: matter-motion-battery +name: motion-battery components: - id: main capabilities: diff --git a/drivers/SmartThings/matter-sensor/profiles/motion-illuminance-battery.yml b/drivers/SmartThings/matter-sensor/profiles/motion-illuminance-battery.yml new file mode 100644 index 0000000000..38f57b172c --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/motion-illuminance-battery.yml @@ -0,0 +1,16 @@ +name: motion-illuminance-battery +components: +- id: main + capabilities: + - id: motionSensor + version: 1 + - id: battery + version: 1 + - id: illuminanceMeasurement + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: MotionSensor \ No newline at end of file diff --git a/drivers/SmartThings/matter-sensor/src/init.lua b/drivers/SmartThings/matter-sensor/src/init.lua index e913806d6d..b05fb66a2b 100644 --- a/drivers/SmartThings/matter-sensor/src/init.lua +++ b/drivers/SmartThings/matter-sensor/src/init.lua @@ -23,6 +23,46 @@ local function device_init(driver, device) device:subscribe() end +local function do_configure(driver, device) + local battery_eps = device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) + local profile_name = "" + + if device:supports_capability(capabilities.motionSensor) then + profile_name = profile_name .. "-motion" + end + + if device:supports_capability(capabilities.contactSensor) then + profile_name = profile_name .. "-contact" + end + + if device:supports_capability(capabilities.illuminanceMeasurement) then + profile_name = profile_name .. "-illuminance" + end + + if device:supports_capability(capabilities.temperatureMeasurement) then + profile_name = profile_name .. "-temperature" + end + + if device:supports_capability(capabilities.relativeHumidityMeasurement) then + profile_name = profile_name .. "-humidity" + end + + if #battery_eps > 0 then + profile_name = profile_name .. "-battery" + end + + -- remove leading "-" + profile_name = string.sub(profile_name, 2) + + device:try_update_metadata({profile = profile_name}) +end + +local function info_changed(driver, device, event, args) + if device.profile.id ~= args.old_st_store.profile.id then + device:subscribe() + end +end + local function illuminance_attr_handler(driver, device, ib, response) local lux = math.floor(10 ^ ((ib.data.value - 1) / 10000)) device:emit_event_for_endpoint(ib.endpoint_id, capabilities.illuminanceMeasurement.illuminance(lux)) @@ -60,7 +100,9 @@ end local matter_driver_template = { lifecycle_handlers = { - init = device_init + init = device_init, + doConfigure = do_configure, + infoChanged = info_changed }, matter_handlers = { attr = { diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_featuremap.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_featuremap.lua new file mode 100644 index 0000000000..ef177bcb6d --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_featuremap.lua @@ -0,0 +1,186 @@ +-- Copyright 2023 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 t_utils = require "integration_test.utils" +local clusters = require "st.matter.clusters" + +local mock_device_humidity_battery = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("humidity-battery.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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.RelativeHumidityMeasurement.ID, cluster_type = "SERVER"}, + } + }, + { + endpoint_id = 2, + clusters = { + {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER", feature_map = 2}, + } + } + } +}) + +local mock_device_humidity_no_battery = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("humidity-battery.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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.RelativeHumidityMeasurement.ID, cluster_type = "SERVER"}, + } + } + } +}) + +local mock_device_temp_humidity = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("temperature-humidity.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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.RelativeHumidityMeasurement.ID, cluster_type = "SERVER"}, + } + }, + { + endpoint_id = 2, + clusters = { + {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "BOTH"}, + } + } + } +}) + +local cluster_subscribe_list_humidity_battery = { + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue, + clusters.PowerSource.attributes.BatPercentRemaining +} + +local cluster_subscribe_list_humidity_no_battery = { + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue +} + +local cluster_subscribe_list_temp_humidity = { + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MeasuredValue +} + +local function test_init_humidity_battery() + local subscribe_request_humidity_battery = cluster_subscribe_list_humidity_battery[1]:subscribe(mock_device_humidity_battery) + for i, cluster in ipairs(cluster_subscribe_list_humidity_battery) do + if i > 1 then + subscribe_request_humidity_battery:merge(cluster:subscribe(mock_device_humidity_battery)) + end + end + + test.socket.matter:__expect_send({mock_device_humidity_battery.id, subscribe_request_humidity_battery}) + test.mock_device.add_test_device(mock_device_humidity_battery) +end + +local function test_init_humidity_no_battery() + local subscribe_request_humidity_no_battery = cluster_subscribe_list_humidity_no_battery[1]:subscribe(mock_device_humidity_no_battery) + for i, cluster in ipairs(cluster_subscribe_list_humidity_no_battery) do + if i > 1 then + subscribe_request_humidity_no_battery:merge(cluster:subscribe(mock_device_humidity_no_battery)) + end + end + + test.socket.matter:__expect_send({mock_device_humidity_no_battery.id, subscribe_request_humidity_no_battery}) + test.mock_device.add_test_device(mock_device_humidity_no_battery) +end + +local function test_init_temp_humidity() + local subscribe_request_temp_humidity = cluster_subscribe_list_temp_humidity[1]:subscribe(mock_device_temp_humidity) + for i, cluster in ipairs(cluster_subscribe_list_temp_humidity) do + if i > 1 then + subscribe_request_temp_humidity:merge(cluster:subscribe(mock_device_temp_humidity)) + end + end + + test.socket.matter:__expect_send({mock_device_temp_humidity.id, subscribe_request_temp_humidity}) + test.mock_device.add_test_device(mock_device_temp_humidity) +end + +test.register_coroutine_test( + "Profile remains the same for battery-supported devices on doConfigure lifecycle event due to cluster feature map", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_humidity_battery.id, "doConfigure" }) + mock_device_humidity_battery:expect_metadata_update({ profile = "humidity-battery" }) + mock_device_humidity_battery:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { test_init = test_init_humidity_battery } +) + +test.register_coroutine_test( + "Profile change to non-battery profile on doConfigure lifecycle event due to cluster feature map", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_humidity_no_battery.id, "doConfigure" }) + mock_device_humidity_no_battery:expect_metadata_update({ profile = "humidity" }) + mock_device_humidity_no_battery:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { test_init = test_init_humidity_no_battery } +) + +test.register_coroutine_test( + "Profile change to non-battery profile on doConfigure lifecycle event due to cluster feature map", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_temp_humidity.id, "doConfigure" }) + mock_device_temp_humidity:expect_metadata_update({ profile = "temperature-humidity" }) + mock_device_temp_humidity:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { test_init = test_init_temp_humidity } +) + +test.run_registered_tests()