From ad6d869708acbae4bc85c5e89d2e6db2a739f79a Mon Sep 17 00:00:00 2001 From: mh-zwave Date: Mon, 27 Oct 2025 16:13:45 +0100 Subject: [PATCH 01/10] add aeotec door window sensor 8 --- .../SmartThings/zwave-sensor/fingerprints.yml | 5 + .../profiles/aeotec-door-window-sensor-8.yml | 128 +++++++++++++ .../src/aeotec-door-window-sensor-8/init.lua | 168 ++++++++++++++++++ drivers/SmartThings/zwave-sensor/src/init.lua | 1 + .../zwave-sensor/src/preferences.lua | 26 +++ 5 files changed, 328 insertions(+) create mode 100644 drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua diff --git a/drivers/SmartThings/zwave-sensor/fingerprints.yml b/drivers/SmartThings/zwave-sensor/fingerprints.yml index 727f02a2d6..8b3a35fa3e 100644 --- a/drivers/SmartThings/zwave-sensor/fingerprints.yml +++ b/drivers/SmartThings/zwave-sensor/fingerprints.yml @@ -548,6 +548,11 @@ zwaveManufacturer: productType: 0x0100 productId: 0x0082 deviceProfileName: shelly-wave-motion + - id: "aeotec/contact/8" + deviceLabel: Aeotec Door Window Sensor 8 + manufacturerId: 0x0371 + productId: 0x0037 + deviceProfileName: aeotec-door-window-sensor-8 zwaveGeneric: - id: "GenericSensorAlarm" deviceLabel: Z-Wave Sensor diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml new file mode 100644 index 0000000000..18fe821211 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml @@ -0,0 +1,128 @@ +name: aeotec-door-window-sensor-8 +components: +- id: main + capabilities: + - id: contactSensor + version: 1 + - id: temperatureMeasurement + config: + values: + - key: "temperature.value" + range: [-10, 60] + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: dewPoint + version: 1 + - id: moldHealthConcern + version: 1 + - id: tamperAlert + version: 1 + - id: powerSource + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: ContactSensor +preferences: + - name: "parameter1" + title: "1 Set threshold Check Time" + description: "When using battery power, follow this configuration, the minimum time is 30 seconds. When using USB power supply, for real-time detection." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 900 + - name: "parameter2" + title: "2 Min. temperature change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 20 + - name: "parameter3" + title: "3 Min. humidity change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 50 + - name: "parameter4" + title: "4 Enable led indication" + description: "This parameter defines when the green or red LED will indicate events. Disabling all indications may extend battery life. Off means no indications." + required: false + preferenceType: enumeration + definition: + options: + 0: "Off" + 1: "On" + default: 0 + - name: "parameter5" + title: "5 Contact sensor polarity" + description: "This parameter allows to set the states of door/window when the magnet closes to the sensor." + required: false + preferenceType: enumeration + definition: + options: + 0: "Closed = Alarm, Open = Idle" + 1: "Open = Alarm, Closed = Idle" + default: 0 + - name: "parameter13" + title: "13 Mold alarm offset" + desccription: "Increase the humidity threshold." + required: false + preferenceType: integer + definition: + minimum: -10 + maximum : 10 + default: 0 + - name: "parameter23" + title: "23 Low battery threshold" + description: "Report low battery report when level goes under threshold setting." + required: false + preferenceType: integer + definition: + minimum: 10 + maximum : 50 + default: 20 + - name: "parameter24" + title: "24 Periodic Reports" + description: "The period of battery, temperature and humidity report, the minimum time is 30 seconds." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 43200 + - name: "parameter25" + title: "25 Offset value for temperature" + description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F" + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter26" + title: "26 Offset value for Humidity" + description: "Calibrate humidity." + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter64" + title: "64 Temperature Scale" + description: "Scale for auto reports and settings." + required: false + preferenceType: enumeration + definition: + options: + 0: "Celsius" + 1: "Fahrenheit" \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua new file mode 100644 index 0000000000..b968cc8269 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua @@ -0,0 +1,168 @@ +-- Copyright 2025 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" +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +--- @type st.zwave.CommandClass.Battery +local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) +--- @type st.zwave.CommandClass.SensorBinary +local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 2 }) +--- @type st.zwave.CommandClass.Configuration +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) + +local log = require "log" +local utils = require "st.utils" + +local MoldHealthConcern = capabilities.moldHealthConcern +local ContactSensor = capabilities.contactSensor + +local AEOTEC_DOOR_WINDOW_SENSOR_8_FINGERPRINTS = { + { manufacturerId = 0x0371, productId = 0x0037 } -- Aeotec Door Window Sensor 8 EU/US/AU +} + +local function can_handle_aeotec_door_window_sensor_8(opts, driver, device, ...) + for _, fingerprint in ipairs(AEOTEC_DOOR_WINDOW_SENSOR_8_FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("aeotec-door-window-sensor-8") + return true, subdriver + end + end + return false +end + +local function added_handler(driver, device) + device:send(Configuration:Get({ parameter_number = 10 })) + + -- Enable binary sensor report + device:send(Configuration:Set({ + parameter_number = 22, + size = 1, + configuration_value = 1 + })) + + device:emit_event(MoldHealthConcern.supportedMoldValues({"good", "moderate"})) + + device:send(SensorBinary:Get({sensor_type = SensorBinary.sensor_type.GENERAL})) -- Mold + device:send(Battery:Get({})) + + device:emit_event(capabilities.refresh.refresh()) +end + +local function do_refresh(driver, device) + device:send(SensorBinary:Get({sensor_type = SensorBinary.sensor_type.GENERAL})) --Mold + + device:send(Battery:Get({})) + + device:send(Configuration:Get({ parameter_number = 10 })) +end + +local function notification_report_handler(self, device, cmd) + local event + + -- DOOR_WINDOW/TILT + if cmd.args.notification_type == Notification.notification_type.ACCESS_CONTROL then + if cmd.args.event == Notification.event.access_control.WINDOW_DOOR_IS_CLOSED then + event = capabilities.contactSensor.contact.closed() + elseif cmd.args.event == Notification.event.access_control.WINDOW_DOOR_IS_OPEN then + event = capabilities.contactSensor.contact.open() + end + end + + -- POWER + if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then + if cmd.args.event == Notification.event.power_management.AC_MAINS_DISCONNECTED then + event = capabilities.powerSource.powerSource.battery() + elseif cmd.args.event == Notification.event.power_management.AC_MAINS_RE_CONNECTED then + event = capabilities.powerSource.powerSource.mains() + elseif cmd.args.event == Notification.event.power_management.POWER_HAS_BEEN_APPLIED then + device:send(Battery:Get({})) + end + end + + -- MOLD + if cmd.args.notification_type == Notification.notification_type.WEATHER_ALARM then + if cmd.args.event == Notification.event.weather_alarm.STATE_IDLE then + event = capabilities.moldHealthConcern.moldHealthConcern.good() + elseif cmd.args.event == Notification.event.weather_alarm.MOISTURE_ALARM then + event = capabilities.moldHealthConcern.moldHealthConcern.moderate() + end + end + + if (event ~= nil) then + device:emit_event(event) + end +end + +local function sensor_binary_report_handler(self, device, cmd) + local sensorType = cmd.args.sensor_type + local value = cmd.args.sensor_value + local event + + local field_name = "initial_state_set_" .. sensorType + + if not device:get_field(field_name) then + log.debug("sensor_binary_report_handler") + -- MOLD + if sensorType == SensorBinary.sensor_type.GENERAL then + if value == SensorBinary.sensor_value.IDLE then + event = capabilities.moldHealthConcern.moldHealthConcern.good() + elseif value == SensorBinary.sensor_value.DETECTED_AN_EVENT then + event = capabilities.moldHealthConcern.moldHealthConcern.moderate() + end + end + + -- DOOR_WINDOW/TILT + if sensorType == SensorBinary.sensor_type.DOOR_WINDOW or sensorType == SensorBinary.sensor_type.TILT then + if value == SensorBinary.sensor_value.IDLE then + event = capabilities.contactSensor.contact.closed() + elseif value == SensorBinary.sensor_value.DETECTED_AN_EVENT then + event = capabilities.contactSensor.contact.open() + end + end + + if (event ~= nil) then + device:emit_event(event) + device:set_field(field_name, true) + end + end +end + +local aeotec_door_window_sensor_8 = { + supported_capabilities = { + capabilities.powerSource, + }, + zwave_handlers = { + [cc.NOTIFICATION] = { + [Notification.REPORT] = notification_report_handler + }, + [cc.SENSOR_BINARY] = { + [SensorBinary.REPORT] = sensor_binary_report_handler + }, + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + lifecycle_handlers = { + added = added_handler, + }, + NAME = "Aeotec Door Window Sesnor 8", + can_handle = can_handle_aeotec_door_window_sensor_8 +} + +return aeotec_door_window_sensor_8 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/init.lua b/drivers/SmartThings/zwave-sensor/src/init.lua index d92a09fe56..b7d3c080bc 100644 --- a/drivers/SmartThings/zwave-sensor/src/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/init.lua @@ -153,6 +153,7 @@ local driver_template = { lazy_load_if_possible("timed-tamper-clear"), lazy_load_if_possible("wakeup-no-poll"), lazy_load_if_possible("apiv6_bugfix"), + lazy_load_if_possible("aeotec-door-window-sensor-8"), }, lifecycle_handlers = { added = added_handler, diff --git a/drivers/SmartThings/zwave-sensor/src/preferences.lua b/drivers/SmartThings/zwave-sensor/src/preferences.lua index 4921a996bf..da69e954c6 100644 --- a/drivers/SmartThings/zwave-sensor/src/preferences.lua +++ b/drivers/SmartThings/zwave-sensor/src/preferences.lua @@ -162,7 +162,33 @@ local devices = { motionNotdetRepT = {parameter_number = 160, size = 2}, }, }, + AEOTEC_DOOR_WINDOW_SENSOR_8 = { + MATCHING_MATRIX = { + mfrs = 0x0371, + product_types = {0x0000, 0x0001, 0x0002}, + product_ids = {0x0037} + }, + PARAMETERS = { + parameter1 = {parameter_number = 1, size = 1}, + parameter2 = {parameter_number = 2, size = 1}, + parameter3 = {parameter_number = 3, size = 1}, + parameter4 = {parameter_number = 4, size = 1}, + parameter5 = {parameter_number = 5, size = 1}, + parameter13 = {parameter_number = 13, size = 1}, + parameter23 = {parameter_number = 23, size = 1}, + parameter24 = {parameter_number = 24, size = 4}, + parameter25 = {parameter_number = 25, size = 2}, + parameter26 = {parameter_number = 26, size = 2}, + parameter27 = {parameter_number = 27, size = 1}, + parameter28 = {parameter_number = 28, size = 1}, + parameter33 = {parameter_number = 33, size = 1}, + parameter34 = {parameter_number = 34, size = 1}, + parameter35 = {parameter_number = 35, size = 1}, + parameter64 = {parameter_number = 64, size = 1}, + }, + }, } + local preferences = {} preferences.update_preferences = function(driver, device, args) From c3b353ab922039763b232e4c3825e8bf404456a1 Mon Sep 17 00:00:00 2001 From: mh-zwave Date: Mon, 2 Feb 2026 09:56:17 +0100 Subject: [PATCH 02/10] update --- .../profiles/aeotec-door-window-sensor-8.yml | 89 +++- .../src/aeotec-door-window-sensor-8/init.lua | 127 +++--- .../zwave-sensor/src/preferences.lua | 4 +- .../test/test_aeotec_door_window_sesnor_8.lua | 397 ++++++++++++++++++ 4 files changed, 540 insertions(+), 77 deletions(-) create mode 100644 drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml index 18fe821211..b741e201ba 100644 --- a/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml @@ -6,9 +6,9 @@ components: version: 1 - id: temperatureMeasurement config: - values: - - key: "temperature.value" - range: [-10, 60] + values: + - key: "temperature.value" + range: [-10, 60] version: 1 - id: relativeHumidityMeasurement version: 1 @@ -20,12 +20,18 @@ components: version: 1 - id: powerSource version: 1 + - id: threeAxis + version: 1 - id: battery version: 1 - id: refresh version: 1 - categories: +categories: - name: ContactSensor +metadata: + deviceType: ContactSensor + ocfDeviceType: x.com.st.d.sensor.contact + deviceTypeId: ContactSensor preferences: - name: "parameter1" title: "1 Set threshold Check Time" @@ -52,25 +58,15 @@ preferences: minimum: 0 maximum : 255 default: 50 - - name: "parameter4" - title: "4 Enable led indication" - description: "This parameter defines when the green or red LED will indicate events. Disabling all indications may extend battery life. Off means no indications." - required: false - preferenceType: enumeration - definition: - options: - 0: "Off" - 1: "On" - default: 0 - name: "parameter5" - title: "5 Contact sensor polarity" + title: "5 State when the magnet is close" description: "This parameter allows to set the states of door/window when the magnet closes to the sensor." required: false preferenceType: enumeration definition: options: - 0: "Closed = Alarm, Open = Idle" - 1: "Open = Alarm, Closed = Idle" + 0: "Open=magnet far, Closed=magnet near" + 1: "Closed=magnet far, Open=magnet near" default: 0 - name: "parameter13" title: "13 Mold alarm offset" @@ -101,7 +97,7 @@ preferences: default: 43200 - name: "parameter25" title: "25 Offset value for temperature" - description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F" + description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F." required: false preferenceType: integer definition: @@ -117,6 +113,63 @@ preferences: minimum: -200 maximum : 200 default: 0 + - name: "parameter27" + title: "27 Set tilt sensor mode" + description: "Set tilt sensor mode." + required: false + preferenceType: enumeration + definition: + options: + 0: "Disable" + 1: "Enabled. Needs magnet" + 2: "Enabled. It can used without magnet." + default: 1 + - name: "parameter28" + title: "28 State of tilt in Mode 2" + description: "This parameter allows setting the state of door/window when the sensor is tilted." + required: false + preferenceType: enumeration + definition: + options: + 0: "Opened = tilted, closed = not tilted" + 1: "Closed = tilted, opened = not tilted" + default: 0 + - name: "parameter33" + title: "33 Tilt triggered angle" + description: "With this parameter, you can adjust the tilt triggered angle if the tilt is too low or too strong." + required: false + preferenceType: integer + definition: + minimum: 1 + maximum : 90 + default: 5 + - name: "parameter34" + title: "34 Timeout tilt detection Mode 1" + description: "Set the timeout of tilt detection Mode 1." + required: false + preferenceType: integer + definition: + minimum: 5 + maximum : 60 + default: 5 + - name: "parameter35" + title: "35 Timeout tilt detection Mode 2" + description: "Set the timeout of tilt detection Mode 2." + required: false + preferenceType: integer + definition: + minimum: 5 + maximum : 60 + default: 8 + - name: "parameter36" + title: "36 Min. acc. change to report" + description: "Minimum acceleration change to trigger report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 0 - name: "parameter64" title: "64 Temperature Scale" description: "Scale for auto reports and settings." diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua index b968cc8269..f28ffdcf4a 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua @@ -19,16 +19,18 @@ local cc = require "st.zwave.CommandClass" local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) --- @type st.zwave.CommandClass.Battery local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) ---- @type st.zwave.CommandClass.SensorBinary -local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 2 }) +--- @type st.zwave.CommandClass.SensorMultilevel +local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ version = 11 }) --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) local log = require "log" -local utils = require "st.utils" local MoldHealthConcern = capabilities.moldHealthConcern local ContactSensor = capabilities.contactSensor +local PowerSource = capabilities.powerSource +local ThreeAxis = capabilities.threeAxis +local TamperAlert = capabilities.tamperAlert local AEOTEC_DOOR_WINDOW_SENSOR_8_FINGERPRINTS = { { manufacturerId = 0x0371, productId = 0x0037 } -- Aeotec Door Window Sensor 8 EU/US/AU @@ -47,47 +49,45 @@ end local function added_handler(driver, device) device:send(Configuration:Get({ parameter_number = 10 })) - -- Enable binary sensor report - device:send(Configuration:Set({ - parameter_number = 22, - size = 1, - configuration_value = 1 - })) - device:emit_event(MoldHealthConcern.supportedMoldValues({"good", "moderate"})) + + -- Default value + device:emit_event(MoldHealthConcern.moldHealthConcern.good()) - device:send(SensorBinary:Get({sensor_type = SensorBinary.sensor_type.GENERAL})) -- Mold + -- Default value + device:emit_event(PowerSource.powerSource.battery()) + device:send(Battery:Get({})) +end - device:emit_event(capabilities.refresh.refresh()) +local function device_init(driver, device) + device:set_field("three_axis_x", 0) + device:set_field("three_axis_y", 0) + device:set_field("three_axis_z", 0) end local function do_refresh(driver, device) - device:send(SensorBinary:Get({sensor_type = SensorBinary.sensor_type.GENERAL})) --Mold - device:send(Battery:Get({})) - - device:send(Configuration:Get({ parameter_number = 10 })) end local function notification_report_handler(self, device, cmd) local event - -- DOOR_WINDOW/TILT + -- DOOR_WINDOW if cmd.args.notification_type == Notification.notification_type.ACCESS_CONTROL then if cmd.args.event == Notification.event.access_control.WINDOW_DOOR_IS_CLOSED then - event = capabilities.contactSensor.contact.closed() + event = ContactSensor.contact.closed() elseif cmd.args.event == Notification.event.access_control.WINDOW_DOOR_IS_OPEN then - event = capabilities.contactSensor.contact.open() + event = ContactSensor.contact.open() end end -- POWER if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then if cmd.args.event == Notification.event.power_management.AC_MAINS_DISCONNECTED then - event = capabilities.powerSource.powerSource.battery() + event = PowerSource.powerSource.battery() elseif cmd.args.event == Notification.event.power_management.AC_MAINS_RE_CONNECTED then - event = capabilities.powerSource.powerSource.mains() + event = PowerSource.powerSource.mains() elseif cmd.args.event == Notification.event.power_management.POWER_HAS_BEEN_APPLIED then device:send(Battery:Get({})) end @@ -96,9 +96,20 @@ local function notification_report_handler(self, device, cmd) -- MOLD if cmd.args.notification_type == Notification.notification_type.WEATHER_ALARM then if cmd.args.event == Notification.event.weather_alarm.STATE_IDLE then - event = capabilities.moldHealthConcern.moldHealthConcern.good() + event = MoldHealthConcern.moldHealthConcern.good() elseif cmd.args.event == Notification.event.weather_alarm.MOISTURE_ALARM then - event = capabilities.moldHealthConcern.moldHealthConcern.moderate() + event = MoldHealthConcern.moldHealthConcern.moderate() + end + end + + -- TAMPER + if cmd.args.notification_type == Notification.notification_type.HOME_SECURITY then + if cmd.args.event == Notification.event.home_security.STATE_IDLE then + log.info("STATE_IDLE") + event = TamperAlert.tamper.clear() + elseif cmd.args.event == Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED then + log.info("TAMPERING_PRODUCT_COVER_REMOVED") + event = TamperAlert.tamper.detected() end end @@ -107,51 +118,52 @@ local function notification_report_handler(self, device, cmd) end end -local function sensor_binary_report_handler(self, device, cmd) - local sensorType = cmd.args.sensor_type - local value = cmd.args.sensor_value +local function sensor_multilevel_report_handler(self, device, cmd) local event + local sensor_type = cmd.args.sensor_type + local value = cmd.args.sensor_value + + local x = device:get_field("three_axis_x") or 0 + local y = device:get_field("three_axis_y") or 0 + local z = device:get_field("three_axis_z") or 0 + + local MIN_VAL = -10000 + local MAX_VAL = 10000 + -- log.info(string.format("SensorMultilevel: type=%d, raw=%.1f", sensor_type, value)) + value = math.max(MIN_VAL, math.min(MAX_VAL, value)) + -- log.info(string.format("Clamped: %.1f", value)) + + if (sensor_type == SensorMultilevel.sensor_type.ACCELERATION_X_AXIS) then + x = value + device:set_field("three_axis_x", x) + event = ThreeAxis.threeAxis(x, y, z) + elseif (sensor_type == SensorMultilevel.sensor_type.ACCELERATION_Y_AXIS) then + y = value + device:set_field("three_axis_y", y) + event = ThreeAxis.threeAxis(x, y, z) + elseif (sensor_type == SensorMultilevel.sensor_type.ACCELERATION_Z_AXIS) then + z = value + device:set_field("three_axis_z", z) + event = ThreeAxis.threeAxis(x, y, z) + end - local field_name = "initial_state_set_" .. sensorType - - if not device:get_field(field_name) then - log.debug("sensor_binary_report_handler") - -- MOLD - if sensorType == SensorBinary.sensor_type.GENERAL then - if value == SensorBinary.sensor_value.IDLE then - event = capabilities.moldHealthConcern.moldHealthConcern.good() - elseif value == SensorBinary.sensor_value.DETECTED_AN_EVENT then - event = capabilities.moldHealthConcern.moldHealthConcern.moderate() - end - end - - -- DOOR_WINDOW/TILT - if sensorType == SensorBinary.sensor_type.DOOR_WINDOW or sensorType == SensorBinary.sensor_type.TILT then - if value == SensorBinary.sensor_value.IDLE then - event = capabilities.contactSensor.contact.closed() - elseif value == SensorBinary.sensor_value.DETECTED_AN_EVENT then - event = capabilities.contactSensor.contact.open() - end - end - - if (event ~= nil) then - device:emit_event(event) - device:set_field(field_name, true) - end + if (event ~= nil) then + device:emit_event(event) end end local aeotec_door_window_sensor_8 = { supported_capabilities = { capabilities.powerSource, + capabilities.threeAxis, }, zwave_handlers = { [cc.NOTIFICATION] = { [Notification.REPORT] = notification_report_handler }, - [cc.SENSOR_BINARY] = { - [SensorBinary.REPORT] = sensor_binary_report_handler - }, + -- [cc.SENSOR_MULTILEVEL] = { + -- [SensorMultilevel.REPORT] = sensor_multilevel_report_handler + -- } }, capability_handlers = { [capabilities.refresh.ID] = { @@ -159,9 +171,10 @@ local aeotec_door_window_sensor_8 = { } }, lifecycle_handlers = { - added = added_handler, + init = device_init, + added = added_handler }, - NAME = "Aeotec Door Window Sesnor 8", + NAME = "Aeotec Door Window Sensor 8", can_handle = can_handle_aeotec_door_window_sensor_8 } diff --git a/drivers/SmartThings/zwave-sensor/src/preferences.lua b/drivers/SmartThings/zwave-sensor/src/preferences.lua index da69e954c6..ea2d45f7b7 100644 --- a/drivers/SmartThings/zwave-sensor/src/preferences.lua +++ b/drivers/SmartThings/zwave-sensor/src/preferences.lua @@ -169,10 +169,9 @@ local devices = { product_ids = {0x0037} }, PARAMETERS = { - parameter1 = {parameter_number = 1, size = 1}, + parameter1 = {parameter_number = 1, size = 4}, parameter2 = {parameter_number = 2, size = 1}, parameter3 = {parameter_number = 3, size = 1}, - parameter4 = {parameter_number = 4, size = 1}, parameter5 = {parameter_number = 5, size = 1}, parameter13 = {parameter_number = 13, size = 1}, parameter23 = {parameter_number = 23, size = 1}, @@ -184,6 +183,7 @@ local devices = { parameter33 = {parameter_number = 33, size = 1}, parameter34 = {parameter_number = 34, size = 1}, parameter35 = {parameter_number = 35, size = 1}, + parameter36 = {parameter_number = 36, size = 1}, parameter64 = {parameter_number = 64, size = 1}, }, }, diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua new file mode 100644 index 0000000000..1ccc9ec880 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua @@ -0,0 +1,397 @@ +-- Copyright 2025 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 zw = require "st.zwave" +local zw_test_utils = require "integration_test.zwave_test_utils" +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +--- @type st.zwave.CommandClass.Battery +local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) +--- @type st.zwave.CommandClass.SensorMultilevel +local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ version = 11 }) +--- @type st.zwave.CommandClass.Configuration +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) +local t_utils = require "integration_test.utils" + +local sensor_endpoints = { + { + command_classes = { + {value = zw.BATTERY}, + {value = zw.NOTIFICATION}, + {value = zw.SENSOR_MULTILEVEL}, + {value = zw.CONFIGURATION} + } + } +} + +local mock_sensor = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("aeotec-door-window-sensor-8.yml"), + zwave_endpoints = sensor_endpoints, + zwave_manufacturer_id = 0x0371, + zwave_product_id = 0x0037, +}) + +local function test_init() + test.mock_device.add_test_device(mock_sensor) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Device added lifecycle event for profile", + function() + test.socket.device_lifecycle:__queue_receive({ mock_sensor.id, "added" }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_sensor, + Configuration:Get({ + parameter_number = 10 + }) + ) + ) + test.socket.capability:__expect_send( + mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.supportedMoldValues({"good", "moderate"})) + ) + + test.socket.capability:__expect_send( + mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) + ) + + test.socket.capability:__expect_send( + mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.battery()) + ) + + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_sensor, + Battery:Get({}) + ) + ) + end +) + +test.register_message_test( + "Refresh should generate the correct commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_sensor.id, + { capability = "refresh", command = "refresh", args = {} } + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_sensor, + Battery:Get({}) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- test.register_message_test( +-- "Notification report STATE_IDLE event should be handled as tamperAlert clear", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.HOME_SECURITY, +-- event = Notification.event.home_security.STATE_IDLE, +-- })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Notification report TAMPERING_PRODUCT_COVER_REMOVED event should be handled as tamperAlert detected", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.HOME_SECURITY, +-- event = Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED, +-- })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Battery report should be handled", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Battery:Report({ battery_level = 0x63 })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.battery.battery(99)) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Notification report AC_MAINS_DISCONNECTED event should be handled power source state battery", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.POWER_MANAGEMENT, +-- event = Notification.event.power_management.AC_MAINS_DISCONNECTED, +-- })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.battery()) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Notification report AC_MAINS_RE_CONNECTED event should be handled power source state dc", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.POWER_MANAGEMENT, +-- event = Notification.event.power_management.AC_MAINS_RE_CONNECTED, +-- })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.dc()) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Notification report POWER_HAS_BEEN_APPLIED event should be send battery get", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.POWER_MANAGEMENT, +-- event = Notification.event.power_management.POWER_HAS_BEEN_APPLIED, +-- })) } +-- }, +-- { +-- channel = "zwave", +-- direction = "send", +-- message = zw_test_utils.zwave_test_build_send_command( +-- mock_sensor, +-- Battery:Get({}) +-- ) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Notification report WINDOW_DOOR_IS_OPEN event should be handled contact sensor state open", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.ACCESS_CONTROL, +-- event = Notification.event.access_control.WINDOW_DOOR_IS_OPEN, +-- })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.contactSensor.contact.open()) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Notification report WINDOW_DOOR_IS_CLOSED event should be handled contact sensor state closed", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.ACCESS_CONTROL, +-- event = Notification.event.access_control.WINDOW_DOOR_IS_CLOSED, +-- })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.contactSensor.contact.closed()) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Temperature reports should be handled", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ +-- sensor_type = SensorMultilevel.sensor_type.TEMPERATURE, +-- scale = SensorMultilevel.scale.temperature.CELSIUS, +-- sensor_value = 21.5 })) +-- } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' })) +-- }, +-- } +-- ) + +-- test.register_message_test( +-- "Humidity reports should be handled", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ +-- sensor_type = SensorMultilevel.sensor_type.RELATIVE_HUMIDITY, +-- sensor_value = 70 })) +-- } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 70, })) +-- }, +-- } +-- ) + +-- test.register_message_test( +-- "Sensor multilevel reports dew_point type command should be handled as dew point measurement", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ +-- sensor_type = SensorMultilevel.sensor_type.DEW_POINT, +-- sensor_value = 8, +-- scale = 0 +-- })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.dewPoint.dewpoint({value = 8, unit = "C"})) +-- } +-- } +-- ) + +-- test.register_coroutine_test( +-- "Three Axis reports should be correctly handled", +-- function() +-- test.socket.zwave:__queue_receive({ +-- mock_sensor.id, +-- SensorMultilevel:Report({ +-- sensor_type = SensorMultilevel.sensor_type.ACCELERATION_X_AXIS, +-- sensor_value = 1.962, +-- scale = SensorMultilevel.scale.acceleration_x_axis.METERS_PER_SQUARE_SECOND } +-- ) +-- }) +-- test.socket.zwave:__queue_receive({ +-- mock_sensor.id, +-- SensorMultilevel:Report({ +-- sensor_type = SensorMultilevel.sensor_type.ACCELERATION_Y_AXIS, +-- sensor_value = 1.962, +-- scale = SensorMultilevel.scale.acceleration_y_axis.METERS_PER_SQUARE_SECOND } +-- ) +-- }) +-- test.socket.zwave:__queue_receive({ +-- mock_sensor.id, +-- SensorMultilevel:Report({ +-- sensor_type = SensorMultilevel.sensor_type.ACCELERATION_Z_AXIS, +-- sensor_value = 3.924, +-- scale = SensorMultilevel.scale.acceleration_z_axis.METERS_PER_SQUARE_SECOND } +-- ) +-- }) +-- test.socket.capability:__expect_send( +-- mock_sensor:generate_test_message("main", +-- capabilities.threeAxis.threeAxis({value = {200, 200, 400}, unit = 'mG'}) +-- ) +-- ) +-- end +-- ) + + +-- test.register_message_test( +-- "Notification report type WEATHER_ALARM event STATE_IDLE should be handled mold healt concern state good", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.WEATHER_ALARM, +-- event = Notification.event.weather_alarm.STATE_IDLE, +-- }))} +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Notification report type WEATHER_ALARM event MOISTURE_ALARM should be handled mold healt concern state moderate", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.WEATHER_ALARM, +-- event = Notification.event.weather_alarm.MOISTURE_ALARM, +-- }))} +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) +-- } +-- } +-- ) + +test.run_registered_tests() \ No newline at end of file From 345ab475a9c24b6e8323f98d1ca1fae4ac7f300e Mon Sep 17 00:00:00 2001 From: mh-zwave Date: Mon, 9 Mar 2026 14:29:38 +0100 Subject: [PATCH 03/10] update powersource capability - add powersource enabled values to device profile - switch powersource from mains to dc --- .../zwave-sensor/profiles/aeotec-door-window-sensor-8.yml | 6 ++++++ .../zwave-sensor/src/aeotec-door-window-sensor-8/init.lua | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml index b741e201ba..03aac15343 100644 --- a/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml @@ -19,6 +19,12 @@ components: - id: tamperAlert version: 1 - id: powerSource + config: + values: + - key: "powerSource.value" + enabledValues: + - battery + - dc version: 1 - id: threeAxis version: 1 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua index f28ffdcf4a..d2a9e49cee 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua @@ -87,7 +87,7 @@ local function notification_report_handler(self, device, cmd) if cmd.args.event == Notification.event.power_management.AC_MAINS_DISCONNECTED then event = PowerSource.powerSource.battery() elseif cmd.args.event == Notification.event.power_management.AC_MAINS_RE_CONNECTED then - event = PowerSource.powerSource.mains() + event = PowerSource.powerSource.dc() elseif cmd.args.event == Notification.event.power_management.POWER_HAS_BEEN_APPLIED then device:send(Battery:Get({})) end From 2784a9a7e18ad60c165a2be5e299b838f0bbd610 Mon Sep 17 00:00:00 2001 From: mh-zwave Date: Thu, 26 Mar 2026 13:28:34 +0100 Subject: [PATCH 04/10] Squashed commit of the following: commit df178e75a2c3bc8b1819699757697a5cf9a14b83 Author: mh-zwave Date: Mon Feb 2 12:58:36 2026 +0100 add test commit e7d73906676bd0476439bc0cb62c84b3218f1c33 Author: mh-zwave Date: Mon Feb 2 12:58:15 2026 +0100 update commit f3a02ae2c2e0944daf9e328401f3b2b1c60460a1 Author: mh-zwave Date: Tue Oct 21 15:33:03 2025 +0200 add missing categorie to device profile commit 5d12d1bf21d68347cc1c14f09587e867afcea9a4 Author: mh-zwave Date: Tue Oct 21 14:39:58 2025 +0200 add Aeotec aerQ --- .../SmartThings/zwave-sensor/fingerprints.yml | 5 + .../zwave-sensor/profiles/aeotec-aerq-8.yml | 117 +++++++ .../zwave-sensor/src/aeotec-aerq-8/init.lua | 113 +++++++ drivers/SmartThings/zwave-sensor/src/init.lua | 1 + .../zwave-sensor/src/preferences.lua | 23 +- .../src/test/test_aeotec_aerq_8.lua | 285 ++++++++++++++++++ 6 files changed, 542 insertions(+), 2 deletions(-) create mode 100644 drivers/SmartThings/zwave-sensor/profiles/aeotec-aerq-8.yml create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-aerq-8/init.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/test/test_aeotec_aerq_8.lua diff --git a/drivers/SmartThings/zwave-sensor/fingerprints.yml b/drivers/SmartThings/zwave-sensor/fingerprints.yml index 8b3a35fa3e..53574ff9ed 100644 --- a/drivers/SmartThings/zwave-sensor/fingerprints.yml +++ b/drivers/SmartThings/zwave-sensor/fingerprints.yml @@ -553,6 +553,11 @@ zwaveManufacturer: manufacturerId: 0x0371 productId: 0x0037 deviceProfileName: aeotec-door-window-sensor-8 + - id: aeotec/aerq/8 + deviceLabel: Aeotec aerQ 8 + manufacturerId: 0x0371 + productId: 0x0039 + deviceProfileName: aeotec-aerq-8 zwaveGeneric: - id: "GenericSensorAlarm" deviceLabel: Z-Wave Sensor diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-aerq-8.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-aerq-8.yml new file mode 100644 index 0000000000..9c3ad7ff0e --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-aerq-8.yml @@ -0,0 +1,117 @@ +name: aeotec-aerq-8 +components: +- id: main + capabilities: + - id: temperatureMeasurement + config: + values: + - key: "temperature.value" + range: [-10, 60] + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: dewPoint + version: 1 + - id: moldHealthConcern + version: 1 + - id: tamperAlert + version: 1 + - id: powerSource + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: TempHumiditySensor +preferences: + - name: "parameter1" + title: "1 Set threshold Check Time" + description: "When using battery power, follow this configuration, the minimum time is 30 seconds. When using USB power supply, for real-time detection." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 900 + - name: "parameter2" + title: "2 Min. temperature change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 20 + - name: "parameter3" + title: "3 Min. humidity change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 50 + - name: "parameter4" + title: "4 Enable led indication" + description: "This parameter defines when the green or red LED will indicate events. Disabling all indications may extend battery life. Off means no indications." + required: false + preferenceType: enumeration + definition: + options: + 0: "Off" + 1: "On" + default: 0 + - name: "parameter13" + title: "13 Mold alarm offset" + desccription: "Increase the humidity threshold." + required: false + preferenceType: integer + definition: + minimum: -10 + maximum : 10 + default: 0 + - name: "parameter23" + title: "23 Low battery threshold" + description: "Report low battery report when level goes under threshold setting." + required: false + preferenceType: integer + definition: + minimum: 10 + maximum : 50 + default: 20 + - name: "parameter24" + title: "24 Periodic Reports" + description: "The period of battery, temperature and humidity report, the minimum time is 30 seconds." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 43200 + - name: "parameter25" + title: "25 Offset value for temperature" + description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F" + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter26" + title: "26 Offset value for Humidity" + description: "Calibrate humidity." + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter64" + title: "64 Temperature Scale" + description: "Scale for auto reports and settings." + required: false + preferenceType: enumeration + definition: + options: + 0: "Celsius" + 1: "Fahrenheit" + default: 0 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-aerq-8/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-aerq-8/init.lua new file mode 100644 index 0000000000..5e7de34e62 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-aerq-8/init.lua @@ -0,0 +1,113 @@ +-- Copyright 2025 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" +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +--- @type st.zwave.CommandClass.Battery +local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) +--- @type st.zwave.CommandClass.SensorBinary +local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 2 }) +--- @type st.zwave.CommandClass.Configuration +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) + +local log = require "log" +local utils = require "st.utils" + +local MoldHealthConcern = capabilities.moldHealthConcern + +local AEOTEC_AERQ_FINGERPRINTS = { + { manufacturerId = 0x0371, productId = 0x0039 } -- Aeotec aerQ 8 EU/US/AU +} + +local function can_handle_aeotec_aerq(opts, driver, device, ...) + for _, fingerprint in ipairs(AEOTEC_AERQ_FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("aeotec-aerq") + return true, subdriver + end + end + return false +end + +local function added_handler(driver, device) + device:send(Configuration:Get({ parameter_number = 10 })) + + device:emit_event(MoldHealthConcern.supportedMoldValues({"good", "moderate"})) + + -- Default value + device:emit_event(MoldHealthConcern.moldHealthConcern.good()) + + -- Default value + device:emit_event(PowerSource.powerSource.battery()) + + device:send(Battery:Get({})) +end + +local function do_refresh(driver, device) + device:send(Battery:Get({})) +end + +local function notification_report_handler(self, device, cmd) + local event + + -- POWER + if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then + if cmd.args.event == Notification.event.power_management.AC_MAINS_DISCONNECTED then + event = capabilities.powerSource.powerSource.battery() + elseif cmd.args.event == Notification.event.power_management.AC_MAINS_RE_CONNECTED then + event = capabilities.powerSource.powerSource.mains() + elseif cmd.args.event == Notification.event.power_management.POWER_HAS_BEEN_APPLIED then + device:send(Battery:Get({})) + end + end + + -- MOLD + if cmd.args.notification_type == Notification.notification_type.WEATHER_ALARM then + if cmd.args.event == Notification.event.weather_alarm.STATE_IDLE then + event = capabilities.moldHealthConcern.moldHealthConcern.good() + elseif cmd.args.event == Notification.event.weather_alarm.MOISTURE_ALARM then + event = capabilities.moldHealthConcern.moldHealthConcern.moderate() + end + end + + if (event ~= nil) then + device:emit_event(event) + end +end + +local aeotec_aerq_8 = { + supported_capabilities = { + capabilities.powerSource + }, + zwave_handlers = { + [cc.NOTIFICATION] = { + [Notification.REPORT] = notification_report_handler + }, + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + lifecycle_handlers = { + added = added_handler, + }, + NAME = "Aeotec aerQ 8", + can_handle = can_handle_aeotec_aerq +} + +return aeotec_aerq_8 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/init.lua b/drivers/SmartThings/zwave-sensor/src/init.lua index b7d3c080bc..de3ad95970 100644 --- a/drivers/SmartThings/zwave-sensor/src/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/init.lua @@ -154,6 +154,7 @@ local driver_template = { lazy_load_if_possible("wakeup-no-poll"), lazy_load_if_possible("apiv6_bugfix"), lazy_load_if_possible("aeotec-door-window-sensor-8"), + lazy_load_if_possible("aeotec-aerq-8") }, lifecycle_handlers = { added = added_handler, diff --git a/drivers/SmartThings/zwave-sensor/src/preferences.lua b/drivers/SmartThings/zwave-sensor/src/preferences.lua index ea2d45f7b7..91f54bc6b1 100644 --- a/drivers/SmartThings/zwave-sensor/src/preferences.lua +++ b/drivers/SmartThings/zwave-sensor/src/preferences.lua @@ -162,7 +162,26 @@ local devices = { motionNotdetRepT = {parameter_number = 160, size = 2}, }, }, - AEOTEC_DOOR_WINDOW_SENSOR_8 = { + AEOTEC_AERQ_8 = { + MATCHING_MATRIX = { + mfrs = 0x0371, + product_types = {0x0002, 0x0102, 0x0202}, + product_ids = 0x0018 + }, + PARAMETERS = { + parameter1 = {parameter_number = 1, size = 1}, + parameter2 = {parameter_number = 2, size = 1}, + parameter3 = {parameter_number = 3, size = 1}, + parameter4 = {parameter_number = 4, size = 1}, + parameter13 = {parameter_number = 13, size = 1}, + parameter23 = {parameter_number = 23, size = 1}, + parameter24 = {parameter_number = 24, size = 4}, + parameter25 = {parameter_number = 25, size = 2}, + parameter26 = {parameter_number = 26, size = 2}, + parameter64 = {parameter_number = 64, size = 1}, + } + }, + AEOTEC_DOOR_WINDOW_SENSOR_8 = { MATCHING_MATRIX = { mfrs = 0x0371, product_types = {0x0000, 0x0001, 0x0002}, @@ -186,7 +205,7 @@ local devices = { parameter36 = {parameter_number = 36, size = 1}, parameter64 = {parameter_number = 64, size = 1}, }, - }, + } } local preferences = {} diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_aerq_8.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_aerq_8.lua new file mode 100644 index 0000000000..89acc99b54 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_aerq_8.lua @@ -0,0 +1,285 @@ +-- Copyright 2025 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 zw = require "st.zwave" +local zw_test_utils = require "integration_test.zwave_test_utils" +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +--- @type st.zwave.CommandClass.Battery +local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) +--- @type st.zwave.CommandClass.SensorBinary +local SensorMultilevel = (require "st.zwave.CommandClass.SensorBinary")({ version = 2 }) +--- @type st.zwave.CommandClass.Configuration +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) +local t_utils = require "integration_test.utils" + +local sensor_endpoints = { + { + command_classes = { + {value = zw.BATTERY}, + {value = zw.NOTIFICATION}, + {value = zw.SENSOR_BINARY}, + {value = zw.CONFIGURATION} + } + } +} + +local mock_sensor = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("aeotec-aerq-8.yml"), + zwave_endpoints = sensor_endpoints, + zwave_manufacturer_id = 0x0371, + zwave_product_id = 0x0039, +}) + +local function test_init() + test.mock_device.add_test_device(mock_sensor) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Device added lifecycle event for profile", + function() + test.socket.device_lifecycle:__queue_receive({ mock_sensor.id, "added" }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_sensor, + Configuration:Get({ + parameter_number = 10 + }) + ) + ) + test.socket.capability:__expect_send( + mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.supportedMoldValues({"good", "moderate"})) + ) + + test.socket.capability:__expect_send( + mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) + ) + + test.socket.capability:__expect_send( + mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.battery()) + ) + + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_sensor, + Battery:Get({}) + ) + ) + end +) + +test.register_message_test( + "Refresh should generate the correct commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_sensor.id, + { capability = "refresh", command = "refresh", args = {} } + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_sensor, + Battery:Get({}) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +test.register_message_test( + "Battery report should be handled", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Battery:Report({ battery_level = 0x63 })) } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.battery.battery(99)) + } + } +) + +test.register_message_test( + "Notification report AC_MAINS_DISCONNECTED event should be handled power source state battery", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.AC_MAINS_DISCONNECTED, + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.battery()) + } + } +) + +test.register_message_test( + "Notification report AC_MAINS_RE_CONNECTED event should be handled power source state dc", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.AC_MAINS_RE_CONNECTED, + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.dc()) + } + } +) + +test.register_message_test( + "Notification report POWER_HAS_BEEN_APPLIED event should be send battery get", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.POWER_HAS_BEEN_APPLIED, + })) } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_sensor, + Battery:Get({}) + ) + } + } +) + +test.register_message_test( + "Temperature reports should be handled", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ + sensor_type = SensorMultilevel.sensor_type.TEMPERATURE, + scale = SensorMultilevel.scale.temperature.CELSIUS, + sensor_value = 21.5 })) + } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' })) + }, + } +) + +test.register_message_test( + "Humidity reports should be handled", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ + sensor_type = SensorMultilevel.sensor_type.RELATIVE_HUMIDITY, + sensor_value = 70 })) + } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 70, })) + }, + } +) + +test.register_message_test( + "Sensor multilevel reports dew_point type command should be handled as dew point measurement", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ + sensor_type = SensorMultilevel.sensor_type.DEW_POINT, + sensor_value = 8, + scale = 0 + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.dewPoint.dewpoint({value = 8, unit = "C"})) + } + } +) + +test.register_message_test( + "Notification report type WEATHER_ALARM event STATE_IDLE should be handled mold healt concern state good", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.WEATHER_ALARM, + event = Notification.event.weather_alarm.STATE_IDLE, + }))} + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) + } + } +) + +test.register_message_test( + "Notification report type WEATHER_ALARM event MOISTURE_ALARM should be handled mold healt concern state moderate", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.WEATHER_ALARM, + event = Notification.event.weather_alarm.MOISTURE_ALARM, + }))} + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) + } + } +) + +test.run_registered_tests() \ No newline at end of file From 964f669bb878a06f534a7e2df213bc03f9b77612 Mon Sep 17 00:00:00 2001 From: mh-zwave Date: Thu, 26 Mar 2026 13:31:17 +0100 Subject: [PATCH 05/10] Squashed commit of the following: commit 958a6257bd1c5c59d4f5708d27753664ee50769d Author: mh-zwave Date: Mon Nov 10 14:48:31 2025 +0100 add test commit b42b309a8713da6d5d0c45c1aa7dc1cd33409fb1 Author: mh-zwave Date: Mon Nov 10 14:48:21 2025 +0100 update commit f942b3db316592c904374c6f8932034917c7375b Author: mh-zwave Date: Thu Oct 23 16:04:13 2025 +0200 update water senor 8 commit 997f47a6832feb71cc510bed4ea3726e962fe6cf Author: mh-zwave Date: Tue Oct 21 15:20:15 2025 +0200 add aeotec water sensor 8 --- .../SmartThings/zwave-sensor/fingerprints.yml | 5 + .../profiles/aeotec-water-sensor-8-co.yml | 146 +++++ .../profiles/aeotec-water-sensor-8-co2.yml | 146 +++++ .../aeotec-water-sensor-8-contact.yml | 146 +++++ .../aeotec-water-sensor-8-glass-break.yml | 146 +++++ .../profiles/aeotec-water-sensor-8-motion.yml | 146 +++++ .../profiles/aeotec-water-sensor-8-panic.yml | 146 +++++ .../profiles/aeotec-water-sensor-8-smoke.yml | 146 +++++ .../profiles/aeotec-water-sensor-8.yml | 146 +++++ .../src/aeotec-water-sensor-8/init.lua | 265 +++++++++ drivers/SmartThings/zwave-sensor/src/init.lua | 3 +- .../zwave-sensor/src/preferences.lua | 23 +- .../src/test/test_aeotec_water_sensor_8.lua | 516 ++++++++++++++++++ 13 files changed, 1978 insertions(+), 2 deletions(-) create mode 100644 drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co.yml create mode 100644 drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co2.yml create mode 100644 drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-contact.yml create mode 100644 drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-glass-break.yml create mode 100644 drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-motion.yml create mode 100644 drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-panic.yml create mode 100644 drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-smoke.yml create mode 100644 drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8.yml create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor-8/init.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_8.lua diff --git a/drivers/SmartThings/zwave-sensor/fingerprints.yml b/drivers/SmartThings/zwave-sensor/fingerprints.yml index 53574ff9ed..6551443db9 100644 --- a/drivers/SmartThings/zwave-sensor/fingerprints.yml +++ b/drivers/SmartThings/zwave-sensor/fingerprints.yml @@ -558,6 +558,11 @@ zwaveManufacturer: manufacturerId: 0x0371 productId: 0x0039 deviceProfileName: aeotec-aerq-8 + - id: "aeotec/water/8" + deviceLabel: Aeotec Water Sensor 8 + manufacturerId: 0x0371 + productId: 0x0038 + deviceProfileName: aeotec-water-sensor-8 zwaveGeneric: - id: "GenericSensorAlarm" deviceLabel: Z-Wave Sensor diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co.yml new file mode 100644 index 0000000000..38682e8c96 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co.yml @@ -0,0 +1,146 @@ +name: aeotec-water-sensor-8-co +components: +- id: main + capabilities: + - id: carbonMonoxideDetector + version: 1 + - id: temperatureMeasurement + config: + values: + - key: "temperature.value" + range: [-10, 60] + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: dewPoint + version: 1 + - id: moldHealthConcern + version: 1 + - id: tamperAlert + version: 1 + - id: powerSource + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: SmokeDetector +preferences: + - name: "parameter1" + title: "1 Set threshold Check Time" + description: "When using battery power, follow this configuration, the minimum time is 30 seconds. When using USB power supply, for real-time detection." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 900 + - name: "parameter2" + title: "2 Min. temperature change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 20 + - name: "parameter3" + title: "3 Min. humidity change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 50 + - name: "parameter4" + title: "4 Enable led indication" + description: "This parameter defines when the green or red LED will indicate events. Disabling all indications may extend battery life. Off means no indications." + required: false + preferenceType: enumeration + definition: + options: + 0: "Off" + 1: "On" + default: 0 + - name: "parameter5" + title: "5 Dry contact sensor polarity" + description: "This parameter allows to set the states of alarm when the dry contact is closed" + required: false + preferenceType: enumeration + definition: + options: + 0: "Closed = Alarm, Open = Idle" + 1: "Open = Alarm, Closed = Idle" + default: 0 + - name: "parameter10" + title: "Notification Type" + description: "Set the notification type." + required: false + preferenceType: enumeration + definition: + options: + 0: "Water Alarm" + 1: "Smoke Alarm" + 2: "CO Alarm" + 3: "CO2 Alarm" + 4: "Door/Window Alarm" + 5: "Tilt Alarm" + 6: "Motion Alarm" + 7: "Glass Break Alarm" + 8: "Panic Alarm" + default: 0 + - name: "parameter13" + title: "13 Mold alarm offset" + description: "Increase the humidity threshold." + required: false + preferenceType: integer + definition: + minimum: -10 + maximum : 10 + default: 0 + - name: "parameter23" + title: "23 Low battery threshold" + description: "Report low battery report when level goes under threshold setting." + required: false + preferenceType: integer + definition: + minimum: 10 + maximum : 50 + default: 20 + - name: "parameter24" + title: "24 Periodic Reports" + description: "The period of battery, temperature and humidity report, the minimum time is 30 seconds." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 43200 + - name: "parameter25" + title: "25 Offset value for temperature" + description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F" + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter26" + title: "26 Offset value for Humidity" + description: "Calibrate humidity." + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter64" + title: "64 Temperature Scale" + description: "Scale for auto reports and settings." + required: false + preferenceType: enumeration + definition: + options: + 0: "Celsius" + 1: "Fahrenheit" + default: 0 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co2.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co2.yml new file mode 100644 index 0000000000..a048d5bd8e --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co2.yml @@ -0,0 +1,146 @@ +name: aeotec-water-sensor-8-co2 +components: +- id: main + capabilities: + - id: carbonDioxideHealthConcern + version: 1 + - id: temperatureMeasurement + config: + values: + - key: "temperature.value" + range: [-10, 60] + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: dewPoint + version: 1 + - id: moldHealthConcern + version: 1 + - id: tamperAlert + version: 1 + - id: powerSource + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: SmokeDetector +preferences: + - name: "parameter1" + title: "1 Set threshold Check Time" + description: "When using battery power, follow this configuration, the minimum time is 30 seconds. When using USB power supply, for real-time detection." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 900 + - name: "parameter2" + title: "2 Min. temperature change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 20 + - name: "parameter3" + title: "3 Min. humidity change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 50 + - name: "parameter4" + title: "4 Enable led indication" + description: "This parameter defines when the green or red LED will indicate events. Disabling all indications may extend battery life. Off means no indications." + required: false + preferenceType: enumeration + definition: + options: + 0: "Off" + 1: "On" + default: 0 + - name: "parameter5" + title: "5 Dry contact sensor polarity" + description: "This parameter allows to set the states of alarm when the dry contact is closed" + required: false + preferenceType: enumeration + definition: + options: + 0: "Closed = Alarm, Open = Idle" + 1: "Open = Alarm, Closed = Idle" + default: 0 + - name: "parameter10" + title: "Notification Type" + description: "Set the notification type." + required: false + preferenceType: enumeration + definition: + options: + 0: "Water Alarm" + 1: "Smoke Alarm" + 2: "CO Alarm" + 3: "CO2 Alarm" + 4: "Door/Window Alarm" + 5: "Tilt Alarm" + 6: "Motion Alarm" + 7: "Glass Break Alarm" + 8: "Panic Alarm" + default: 0 + - name: "parameter13" + title: "13 Mold alarm offset" + description: "Increase the humidity threshold." + required: false + preferenceType: integer + definition: + minimum: -10 + maximum : 10 + default: 0 + - name: "parameter23" + title: "23 Low battery threshold" + description: "Report low battery report when level goes under threshold setting." + required: false + preferenceType: integer + definition: + minimum: 10 + maximum : 50 + default: 20 + - name: "parameter24" + title: "24 Periodic Reports" + description: "The period of battery, temperature and humidity report, the minimum time is 30 seconds." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 43200 + - name: "parameter25" + title: "25 Offset value for temperature" + description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F" + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter26" + title: "26 Offset value for Humidity" + description: "Calibrate humidity." + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter64" + title: "64 Temperature Scale" + description: "Scale for auto reports and settings." + required: false + preferenceType: enumeration + definition: + options: + 0: "Celsius" + 1: "Fahrenheit" + default: 0 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-contact.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-contact.yml new file mode 100644 index 0000000000..eb476d9d9c --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-contact.yml @@ -0,0 +1,146 @@ +name: aeotec-water-sensor-8-contact +components: +- id: main + capabilities: + - id: contactSensor + version: 1 + - id: temperatureMeasurement + config: + values: + - key: "temperature.value" + range: [-10, 60] + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: dewPoint + version: 1 + - id: moldHealthConcern + version: 1 + - id: tamperAlert + version: 1 + - id: powerSource + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: ContactSensor +preferences: + - name: "parameter1" + title: "1 Set threshold Check Time" + description: "When using battery power, follow this configuration, the minimum time is 30 seconds. When using USB power supply, for real-time detection." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 900 + - name: "parameter2" + title: "2 Min. temperature change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 20 + - name: "parameter3" + title: "3 Min. humidity change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 50 + - name: "parameter4" + title: "4 Enable led indication" + description: "This parameter defines when the green or red LED will indicate events. Disabling all indications may extend battery life. Off means no indications." + required: false + preferenceType: enumeration + definition: + options: + 0: "Off" + 1: "On" + default: 0 + - name: "parameter5" + title: "5 Dry contact sensor polarity" + description: "This parameter allows to set the states of alarm when the dry contact is closed" + required: false + preferenceType: enumeration + definition: + options: + 0: "Closed = Alarm, Open = Idle" + 1: "Open = Alarm, Closed = Idle" + default: 0 + - name: "parameter10" + title: "Notification Type" + description: "Set the notification type." + required: false + preferenceType: enumeration + definition: + options: + 0: "Water Alarm" + 1: "Smoke Alarm" + 2: "CO Alarm" + 3: "CO2 Alarm" + 4: "Door/Window Alarm" + 5: "Tilt Alarm" + 6: "Motion Alarm" + 7: "Glass Break Alarm" + 8: "Panic Alarm" + default: 0 + - name: "parameter13" + title: "13 Mold alarm offset" + description: "Increase the humidity threshold." + required: false + preferenceType: integer + definition: + minimum: -10 + maximum : 10 + default: 0 + - name: "parameter23" + title: "23 Low battery threshold" + description: "Report low battery report when level goes under threshold setting." + required: false + preferenceType: integer + definition: + minimum: 10 + maximum : 50 + default: 20 + - name: "parameter24" + title: "24 Periodic Reports" + description: "The period of battery, temperature and humidity report, the minimum time is 30 seconds." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 43200 + - name: "parameter25" + title: "25 Offset value for temperature" + description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F" + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter26" + title: "26 Offset value for Humidity" + description: "Calibrate humidity." + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter64" + title: "64 Temperature Scale" + description: "Scale for auto reports and settings." + required: false + preferenceType: enumeration + definition: + options: + 0: "Celsius" + 1: "Fahrenheit" + default: 0 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-glass-break.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-glass-break.yml new file mode 100644 index 0000000000..287efe5bba --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-glass-break.yml @@ -0,0 +1,146 @@ +name: aeotec-water-sensor-8-glass-break +components: +- id: main + capabilities: + - id: soundDetection + version: 1 + - id: temperatureMeasurement + config: + values: + - key: "temperature.value" + range: [-10, 60] + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: dewPoint + version: 1 + - id: moldHealthConcern + version: 1 + - id: tamperAlert + version: 1 + - id: powerSource + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: SoundSensor +preferences: + - name: "parameter1" + title: "1 Set threshold Check Time" + description: "When using battery power, follow this configuration, the minimum time is 30 seconds. When using USB power supply, for real-time detection." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 900 + - name: "parameter2" + title: "2 Min. temperature change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 20 + - name: "parameter3" + title: "3 Min. humidity change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 50 + - name: "parameter4" + title: "4 Enable led indication" + description: "This parameter defines when the green or red LED will indicate events. Disabling all indications may extend battery life. Off means no indications." + required: false + preferenceType: enumeration + definition: + options: + 0: "Off" + 1: "On" + default: 0 + - name: "parameter5" + title: "5 Dry contact sensor polarity" + description: "This parameter allows to set the states of alarm when the dry contact is closed" + required: false + preferenceType: enumeration + definition: + options: + 0: "Closed = Alarm, Open = Idle" + 1: "Open = Alarm, Closed = Idle" + default: 0 + - name: "parameter10" + title: "Notification Type" + description: "Set the notification type." + required: false + preferenceType: enumeration + definition: + options: + 0: "Water Alarm" + 1: "Smoke Alarm" + 2: "CO Alarm" + 3: "CO2 Alarm" + 4: "Door/Window Alarm" + 5: "Tilt Alarm" + 6: "Motion Alarm" + 7: "Glass Break Alarm" + 8: "Panic Alarm" + default: 0 + - name: "parameter13" + title: "13 Mold alarm offset" + description: "Increase the humidity threshold." + required: false + preferenceType: integer + definition: + minimum: -10 + maximum : 10 + default: 0 + - name: "parameter23" + title: "23 Low battery threshold" + description: "Report low battery report when level goes under threshold setting." + required: false + preferenceType: integer + definition: + minimum: 10 + maximum : 50 + default: 20 + - name: "parameter24" + title: "24 Periodic Reports" + description: "The period of battery, temperature and humidity report, the minimum time is 30 seconds." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 43200 + - name: "parameter25" + title: "25 Offset value for temperature" + description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F" + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter26" + title: "26 Offset value for Humidity" + description: "Calibrate humidity." + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter64" + title: "64 Temperature Scale" + description: "Scale for auto reports and settings." + required: false + preferenceType: enumeration + definition: + options: + 0: "Celsius" + 1: "Fahrenheit" + default: 0 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-motion.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-motion.yml new file mode 100644 index 0000000000..d9f2e6ba8b --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-motion.yml @@ -0,0 +1,146 @@ +name: aeotec-water-sensor-8-motion +components: +- id: main + capabilities: + - id: motionSensor + version: 1 + - id: temperatureMeasurement + config: + values: + - key: "temperature.value" + range: [-10, 60] + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: dewPoint + version: 1 + - id: moldHealthConcern + version: 1 + - id: tamperAlert + version: 1 + - id: powerSource + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: MotionSensor +preferences: + - name: "parameter1" + title: "1 Set threshold Check Time" + description: "When using battery power, follow this configuration, the minimum time is 30 seconds. When using USB power supply, for real-time detection." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 900 + - name: "parameter2" + title: "2 Min. temperature change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 20 + - name: "parameter3" + title: "3 Min. humidity change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 50 + - name: "parameter4" + title: "4 Enable led indication" + description: "This parameter defines when the green or red LED will indicate events. Disabling all indications may extend battery life. Off means no indications." + required: false + preferenceType: enumeration + definition: + options: + 0: "Off" + 1: "On" + default: 0 + - name: "parameter5" + title: "5 Dry contact sensor polarity" + description: "This parameter allows to set the states of alarm when the dry contact is closed" + required: false + preferenceType: enumeration + definition: + options: + 0: "Closed = Alarm, Open = Idle" + 1: "Open = Alarm, Closed = Idle" + default: 0 + - name: "parameter10" + title: "Notification Type" + description: "Set the notification type." + required: false + preferenceType: enumeration + definition: + options: + 0: "Water Alarm" + 1: "Smoke Alarm" + 2: "CO Alarm" + 3: "CO2 Alarm" + 4: "Door/Window Alarm" + 5: "Tilt Alarm" + 6: "Motion Alarm" + 7: "Glass Break Alarm" + 8: "Panic Alarm" + default: 0 + - name: "parameter13" + title: "13 Mold alarm offset" + description: "Increase the humidity threshold." + required: false + preferenceType: integer + definition: + minimum: -10 + maximum : 10 + default: 0 + - name: "parameter23" + title: "23 Low battery threshold" + description: "Report low battery report when level goes under threshold setting." + required: false + preferenceType: integer + definition: + minimum: 10 + maximum : 50 + default: 20 + - name: "parameter24" + title: "24 Periodic Reports" + description: "The period of battery, temperature and humidity report, the minimum time is 30 seconds." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 43200 + - name: "parameter25" + title: "25 Offset value for temperature" + description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F" + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter26" + title: "26 Offset value for Humidity" + description: "Calibrate humidity." + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter64" + title: "64 Temperature Scale" + description: "Scale for auto reports and settings." + required: false + preferenceType: enumeration + definition: + options: + 0: "Celsius" + 1: "Fahrenheit" + default: 0 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-panic.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-panic.yml new file mode 100644 index 0000000000..d366cee6b7 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-panic.yml @@ -0,0 +1,146 @@ +name: aeotec-water-sensor-8-panic +components: +- id: main + capabilities: + - id: panicAlarm + version: 1 + - id: temperatureMeasurement + config: + values: + - key: "temperature.value" + range: [-10, 60] + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: dewPoint + version: 1 + - id: moldHealthConcern + version: 1 + - id: tamperAlert + version: 1 + - id: powerSource + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: PanicButton +preferences: + - name: "parameter1" + title: "1 Set threshold Check Time" + description: "When using battery power, follow this configuration, the minimum time is 30 seconds. When using USB power supply, for real-time detection." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 900 + - name: "parameter2" + title: "2 Min. temperature change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 20 + - name: "parameter3" + title: "3 Min. humidity change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 50 + - name: "parameter4" + title: "4 Enable led indication" + description: "This parameter defines when the green or red LED will indicate events. Disabling all indications may extend battery life. Off means no indications." + required: false + preferenceType: enumeration + definition: + options: + 0: "Off" + 1: "On" + default: 0 + - name: "parameter5" + title: "5 Dry contact sensor polarity" + description: "This parameter allows to set the states of alarm when the dry contact is closed" + required: false + preferenceType: enumeration + definition: + options: + 0: "Closed = Alarm, Open = Idle" + 1: "Open = Alarm, Closed = Idle" + default: 0 + - name: "parameter10" + title: "Notification Type" + description: "Set the notification type." + required: false + preferenceType: enumeration + definition: + options: + 0: "Water Alarm" + 1: "Smoke Alarm" + 2: "CO Alarm" + 3: "CO2 Alarm" + 4: "Door/Window Alarm" + 5: "Tilt Alarm" + 6: "Motion Alarm" + 7: "Glass Break Alarm" + 8: "Panic Alarm" + default: 0 + - name: "parameter13" + title: "13 Mold alarm offset" + description: "Increase the humidity threshold." + required: false + preferenceType: integer + definition: + minimum: -10 + maximum : 10 + default: 0 + - name: "parameter23" + title: "23 Low battery threshold" + description: "Report low battery report when level goes under threshold setting." + required: false + preferenceType: integer + definition: + minimum: 10 + maximum : 50 + default: 20 + - name: "parameter24" + title: "24 Periodic Reports" + description: "The period of battery, temperature and humidity report, the minimum time is 30 seconds." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 43200 + - name: "parameter25" + title: "25 Offset value for temperature" + description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F" + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter26" + title: "26 Offset value for Humidity" + description: "Calibrate humidity." + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter64" + title: "64 Temperature Scale" + description: "Scale for auto reports and settings." + required: false + preferenceType: enumeration + definition: + options: + 0: "Celsius" + 1: "Fahrenheit" + default: 0 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-smoke.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-smoke.yml new file mode 100644 index 0000000000..06023a4962 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-smoke.yml @@ -0,0 +1,146 @@ +name: aeotec-water-sensor-8-smoke +components: +- id: main + capabilities: + - id: smokeDetector + version: 1 + - id: temperatureMeasurement + config: + values: + - key: "temperature.value" + range: [-10, 60] + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: dewPoint + version: 1 + - id: moldHealthConcern + version: 1 + - id: tamperAlert + version: 1 + - id: powerSource + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: SmokeDetector +preferences: + - name: "parameter1" + title: "1 Set threshold Check Time" + description: "When using battery power, follow this configuration, the minimum time is 30 seconds. When using USB power supply, for real-time detection." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 900 + - name: "parameter2" + title: "2 Min. temperature change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 20 + - name: "parameter3" + title: "3 Min. humidity change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 50 + - name: "parameter4" + title: "4 Enable led indication" + description: "This parameter defines when the green or red LED will indicate events. Disabling all indications may extend battery life. Off means no indications." + required: false + preferenceType: enumeration + definition: + options: + 0: "Off" + 1: "On" + default: 0 + - name: "parameter5" + title: "5 Dry contact sensor polarity" + description: "This parameter allows to set the states of alarm when the dry contact is closed" + required: false + preferenceType: enumeration + definition: + options: + 0: "Closed = Alarm, Open = Idle" + 1: "Open = Alarm, Closed = Idle" + default: 0 + - name: "parameter10" + title: "Notification Type" + description: "Set the notification type." + required: false + preferenceType: enumeration + definition: + options: + 0: "Water Alarm" + 1: "Smoke Alarm" + 2: "CO Alarm" + 3: "CO2 Alarm" + 4: "Door/Window Alarm" + 5: "Tilt Alarm" + 6: "Motion Alarm" + 7: "Glass Break Alarm" + 8: "Panic Alarm" + default: 0 + - name: "parameter13" + title: "13 Mold alarm offset" + description: "Increase the humidity threshold." + required: false + preferenceType: integer + definition: + minimum: -10 + maximum : 10 + default: 0 + - name: "parameter23" + title: "23 Low battery threshold" + description: "Report low battery report when level goes under threshold setting." + required: false + preferenceType: integer + definition: + minimum: 10 + maximum : 50 + default: 20 + - name: "parameter24" + title: "24 Periodic Reports" + description: "The period of battery, temperature and humidity report, the minimum time is 30 seconds." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 43200 + - name: "parameter25" + title: "25 Offset value for temperature" + description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F" + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter26" + title: "26 Offset value for Humidity" + description: "Calibrate humidity." + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter64" + title: "64 Temperature Scale" + description: "Scale for auto reports and settings." + required: false + preferenceType: enumeration + definition: + options: + 0: "Celsius" + 1: "Fahrenheit" + default: 0 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8.yml new file mode 100644 index 0000000000..0e27580c9f --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8.yml @@ -0,0 +1,146 @@ +name: aeotec-water-sensor-8 +components: +- id: main + capabilities: + - id: waterSensor + version: 1 + - id: temperatureMeasurement + config: + values: + - key: "temperature.value" + range: [-10, 60] + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: dewPoint + version: 1 + - id: moldHealthConcern + version: 1 + - id: tamperAlert + version: 1 + - id: powerSource + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: LeakSensor +preferences: + - name: "parameter1" + title: "1 Set threshold Check Time" + description: "When using battery power, follow this configuration, the minimum time is 30 seconds. When using USB power supply, for real-time detection." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 900 + - name: "parameter2" + title: "2 Min. temperature change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 20 + - name: "parameter3" + title: "3 Min. humidity change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 50 + - name: "parameter4" + title: "4 Enable led indication" + description: "This parameter defines when the green or red LED will indicate events. Disabling all indications may extend battery life. Off means no indications." + required: false + preferenceType: enumeration + definition: + options: + 0: "Off" + 1: "On" + default: 0 + - name: "parameter5" + title: "5 Dry contact sensor polarity" + description: "This parameter allows to set the states of alarm when the dry contact is closed" + required: false + preferenceType: enumeration + definition: + options: + 0: "Closed = Alarm, Open = Idle" + 1: "Open = Alarm, Closed = Idle" + default: 0 + - name: "parameter10" + title: "Notification Type" + description: "Set the notification type." + required: false + preferenceType: enumeration + definition: + options: + 0: "Water Alarm" + 1: "Smoke Alarm" + 2: "CO Alarm" + 3: "CO2 Alarm" + 4: "Door/Window Alarm" + 5: "Tilt Alarm" + 6: "Motion Alarm" + 7: "Glass Break Alarm" + 8: "Panic Alarm" + default: 0 + - name: "parameter13" + title: "13 Mold alarm offset" + description: "Increase the humidity threshold." + required: false + preferenceType: integer + definition: + minimum: -10 + maximum : 10 + default: 0 + - name: "parameter23" + title: "23 Low battery threshold" + description: "Report low battery report when level goes under threshold setting." + required: false + preferenceType: integer + definition: + minimum: 10 + maximum : 50 + default: 20 + - name: "parameter24" + title: "24 Periodic Reports" + description: "The period of battery, temperature and humidity report, the minimum time is 30 seconds." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 43200 + - name: "parameter25" + title: "25 Offset value for temperature" + description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F" + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter26" + title: "26 Offset value for Humidity" + description: "Calibrate humidity." + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter64" + title: "64 Temperature Scale" + description: "Scale for auto reports and settings." + required: false + preferenceType: enumeration + definition: + options: + 0: "Celsius" + 1: "Fahrenheit" + default: 0 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor-8/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor-8/init.lua new file mode 100644 index 0000000000..d6ee9c0f1e --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor-8/init.lua @@ -0,0 +1,265 @@ +-- Copyright 2025 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" +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +--- @type st.zwave.CommandClass.Battery +local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) +--- @type st.zwave.CommandClass.Configuration +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) + +local log = require "log" +local utils = require "st.utils" + +local MoldHealthConcern = capabilities.moldHealthConcern +local CarbonDioxideHealthConcern = capabilities.carbonDioxideHealthConcern +local SoundDetection = capabilities.soundDetection +local SmokeDetector = capabilities.smokeDetector +local WaterSensor = capabilities.waterSensor +local CarbonMonoxideDetector = capabilities.carbonMonoxideDetector +local TamperAlert = capabilities.tamperAlert +local MotionSensor = capabilities.motionSensor +local PowerSource = capabilities.powerSource +local ContactSensor = capabilities.contactSensor +local PanicAlarm = capabilities.panicAlarm + +local AEOTEC_WATER_SENSOR_8_FINGERPRINTS = { + { manufacturerId = 0x0371, productId = 0x0038 } -- Aeotec Water Sensor 8 EU/US/AU +} + +DEVICE_PROFILES = { + [0] = { profile = "aeotec-water-sensor-8"}, + [1] = { profile = "aeotec-water-sensor-8-smoke"}, + [2] = { profile = "aeotec-water-sensor-8-co"}, + [3] = { profile = "aeotec-water-sensor-8-co2"}, + [4] = { profile = "aeotec-water-sensor-8-contact"}, + [5] = { profile = "aeotec-water-sensor-8-contact"}, + [6] = { profile = "aeotec-water-sensor-8-motion"}, + [7] = { profile = "aeotec-water-sensor-8-glass-break"}, + [8] = { profile = "aeotec-water-sensor-8-panic"} +} + +local function can_handle_aeotec_water_sensor_8(opts, driver, device, ...) + for _, fingerprint in ipairs(AEOTEC_WATER_SENSOR_8_FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("aeotec-water-sensor-8") + return true, subdriver + end + end + return false +end + +local function set_profile(device, profile) + local current = device:get_field("active_profile") + if current ~= profile.profile then + log.info(string.format("Switching profile to: %s", profile.profile)) + + device:try_update_metadata({ profile = profile.profile }) + device:set_field("active_profile", profile.profile) + + -- Set supported modes and default value based on profile + if profile.profile == "aeotec-water-sensor-8" then + device:emit_event(WaterSensor.water.dry()) + elseif profile.profile == "aeotec-water-sensor-8-glass-break" then + device:emit_event(SoundDetection.supportedSoundTypes({"noSound", "glassBreaking"})) + device:emit_event(SoundDetection.soundDetected.noSound()) + elseif profile.profile == "aeotec-water-sensor-8-co2" then + device:emit_event(CarbonDioxideHealthConcern.supportedCarbonDioxideValues({"good", "moderate"})) + device:emit_event(CarbonDioxideHealthConcern.carbonDioxideHealthConcern.good()) + elseif profile.profile == "aeotec-water-sensor-8-co" then + device:emit_event(CarbonMonoxideDetector.carbonMonoxide.clear()) + elseif profile.profile == "aeotec-water-sensor-8-contact" then + device:emit_event(ContactSensor.contact.closed()) + elseif profile.profile == "aeotec-water-sensor-8-motion" then + device:emit_event(MotionSensor.motion.inactive()) + elseif profile.profile == "aeotec-water-sensor-8-panic" then + device:emit_event(PanicAlarm.panicAlarm.clear()) + elseif profile.profile == "aeotec-water-sensor-8-smoke" then + device:emit_event(SmokeDetector.smoke.clear()) + end + end +end + +local function added_handler(driver, device) + -- Get parameter 10 to switch device profile bsaed on the parameter value + device:send(Configuration:Get({ parameter_number = 10 })) + + device:emit_event(MoldHealthConcern.supportedMoldValues({"good", "moderate"})) + -- Default value + device:emit_event(MoldHealthConcern.moldHealthConcern.good()) + + -- Default value + device:emit_event(PowerSource.powerSource.battery()) + + device:send(Battery:Get({})) +end + +local function do_refresh(driver, device) + device:send(Battery:Get({})) +end + +local function notification_report_handler(self, device, cmd) + local active_profile = device:get_field("active_profile") + local event + + local event_parameter + log.info("event_parameter", utils.stringify_table(cmd.args.event_parameter)) + + if (0 ~= string.len(cmd.args.event_parameter)) then + event_parameter = string.byte(cmd.args.event_parameter) + end + + -- MOTION, GLASS_BREAK, TAMPER + if cmd.args.notification_type == Notification.notification_type.HOME_SECURITY then + -- TAMPER + if cmd.args.event == Notification.event.home_security.STATE_IDLE and event_parameter == Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED then + event = TamperAlert.tamper.clear() + elseif active_profile == 'aeotec-water-sensor-8-motion' then -- MOTION + if cmd.args.event == Notification.event.home_security.STATE_IDLE and event_parameter == Notification.event.home_security.MOTION_DETECTION then + event = MotionSensor.motion.inactive() + elseif cmd.args.event == Notification.event.home_security.MOTION_DETECTION then + event = MotionSensor.motion.active() + end + elseif active_profile == 'aeotec-water-sensor-8-glass-break' then -- GLASS_BREAK + if cmd.args.event == Notification.event.home_security.STATE_IDLE and event_parameter == Notification.event.home_security.GLASS_BREAKAGE then + event = SoundDetection.soundDetected.noSound() + elseif cmd.args.event == Notification.event.home_security.GLASS_BREAKAGE then + event = SoundDetection.soundDetected.glassBreaking() + end + end + end + + if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then + if cmd.args.event == Notification.event.power_management.AC_MAINS_DISCONNECTED then + event = PowerSource.powerSource.battery() + elseif cmd.args.event == Notification.event.power_management.AC_MAINS_RE_CONNECTED then + event = PowerSource.powerSource.dc() + elseif cmd.args.event == Notification.event.power_management.POWER_HAS_BEEN_APPLIED then + device:send(Battery:Get({})) + end + end + + -- WATER + if cmd.args.notification_type == Notification.notification_type.WATER then + if cmd.args.event == Notification.event.water.STATE_IDLE then + event = WaterSensor.water.dry() + elseif cmd.args.event == Notification.event.water.LEAK_DETECTED then + event = WaterSensor.water.wet() + end + end + + -- MOLD + if cmd.args.notification_type == Notification.notification_type.WEATHER_ALARM then + if cmd.args.event == Notification.event.weather_alarm.STATE_IDLE then + event = MoldHealthConcern.moldHealthConcern.good() + elseif cmd.args.event == Notification.event.weather_alarm.MOISTURE_ALARM then + event = MoldHealthConcern.moldHealthConcern.moderate() + end + end + + -- SMOKE + if cmd.args.notification_type == Notification.notification_type.SMOKE then + if cmd.args.event == Notification.event.smoke.STATE_IDLE then + event = SmokeDetector.smoke.clear() + elseif cmd.args.event == Notification.event.smoke.DETECTED then + event = SmokeDetector.smoke.detected() + end + end + + -- CO + if cmd.args.notification_type == Notification.notification_type.CO then + if cmd.args.event == Notification.event.co.STATE_IDLE then + event = CarbonMonoxideDetector.carbonMonoxide.clear() + elseif cmd.args.event == Notification.event.co.CARBON_MONOXIDE_DETECTED then + event = CarbonMonoxideDetector.carbonMonoxide.detected() + end + end + + -- CO2 + if cmd.args.notification_type == Notification.notification_type.CO2 then + if cmd.args.event == Notification.event.co2.STATE_IDLE then + event = capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern.good() + elseif cmd.args.event == Notification.event.co2.CARBON_DIOXIDE_DETECTED then + event = capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern.moderate() + end + end + + -- DOOR_WINDOW/TILT + if cmd.args.notification_type == Notification.notification_type.ACCESS_CONTROL then + if cmd.args.event == Notification.event.access_control.WINDOW_DOOR_IS_CLOSED then + event = ContactSensor.contact.closed() + elseif cmd.args.event == Notification.event.access_control.WINDOW_DOOR_IS_OPEN then + event = ContactSensor.contact.open() + end + end + + -- PANIC + if cmd.args.notification_type == Notification.notification_type.EMERGENCY then + if cmd.args.event == Notification.event.emergency.STATE_IDLE then + event = PanicAlarm.panicAlarm.clear() + elseif cmd.args.event == Notification.event.emergency.PANIC_ALERT then + event = PanicAlarm.panicAlarm.panic() + end + end + + if (event ~= nil) then + device:emit_event(event) + end +end + +local function configuration_report_handler(self, device, cmd) + local param_number = cmd.args.parameter_number + local value = cmd.args.configuration_value + log.info(string.format("Received Configuration Report #%d = %d", param_number, value)) + + if param_number == 10 then + local mapping = DEVICE_PROFILES[value] + if mapping then + set_profile(device, mapping) + end + end +end + +local aeotec_water_sensor_8 = { + supported_capabilities = { + capabilities.powerSource, + capabilities.carbonMonoxideDetector, + capabilities.carbonDioxideHealthConcern, + capabilities.soundDetection, + capabilities.panicAlarm + }, + zwave_handlers = { + [cc.CONFIGURATION] = { + [Configuration.REPORT] = configuration_report_handler + }, + [cc.NOTIFICATION] = { + [Notification.REPORT] = notification_report_handler + }, + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + lifecycle_handlers = { + added = added_handler, + }, + NAME = "Aeotec Water Sensor 8", + can_handle = can_handle_aeotec_water_sensor_8 +} + +return aeotec_water_sensor_8 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/init.lua b/drivers/SmartThings/zwave-sensor/src/init.lua index de3ad95970..b8cc862cd8 100644 --- a/drivers/SmartThings/zwave-sensor/src/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/init.lua @@ -154,7 +154,8 @@ local driver_template = { lazy_load_if_possible("wakeup-no-poll"), lazy_load_if_possible("apiv6_bugfix"), lazy_load_if_possible("aeotec-door-window-sensor-8"), - lazy_load_if_possible("aeotec-aerq-8") + lazy_load_if_possible("aeotec-aerq-8"), + lazy_load_if_possible("aeotec-water-sensor-8") }, lifecycle_handlers = { added = added_handler, diff --git a/drivers/SmartThings/zwave-sensor/src/preferences.lua b/drivers/SmartThings/zwave-sensor/src/preferences.lua index 91f54bc6b1..e7f67f07a3 100644 --- a/drivers/SmartThings/zwave-sensor/src/preferences.lua +++ b/drivers/SmartThings/zwave-sensor/src/preferences.lua @@ -205,7 +205,28 @@ local devices = { parameter36 = {parameter_number = 36, size = 1}, parameter64 = {parameter_number = 64, size = 1}, }, - } + }, + AEOTEC_WATER_SENSOR_8 = { + MATCHING_MATRIX = { + mfrs = 0x0371, + product_types = {0x0000, 0x0001, 0x0002}, + product_ids = {0x0038} + }, + PARAMETERS = { + parameter1 = {parameter_number = 1, size = 1}, + parameter2 = {parameter_number = 2, size = 1}, + parameter3 = {parameter_number = 3, size = 1}, + parameter4 = {parameter_number = 4, size = 1}, + parameter5 = {parameter_number = 5, size = 1}, + parameter10 = {parameter_number = 10, size = 1}, + parameter13 = {parameter_number = 13, size = 1}, + parameter23 = {parameter_number = 23, size = 1}, + parameter24 = {parameter_number = 24, size = 4}, + parameter25 = {parameter_number = 25, size = 2}, + parameter26 = {parameter_number = 26, size = 2}, + parameter64 = {parameter_number = 64, size = 1}, + }, + }, } local preferences = {} diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_8.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_8.lua new file mode 100644 index 0000000000..04d767d0c6 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_8.lua @@ -0,0 +1,516 @@ +-- Copyright 2025 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 zw = require "st.zwave" +local zw_test_utils = require "integration_test.zwave_test_utils" +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +--- @type st.zwave.CommandClass.Battery +local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) +--- @type st.zwave.CommandClass.Configuration +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) +local t_utils = require "integration_test.utils" + +local sensor_endpoints = { + { + command_classes = { + {value = zw.BATTERY}, + {value = zw.NOTIFICATION}, + {value = zw.SENSOR_MULTILEVEL}, + {value = zw.CONFIGURATION} + } + } +} + +local mock_water_sensor = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("aeotec-water-sensor-8.yml"), + zwave_endpoints = sensor_endpoints, + zwave_manufacturer_id = 0x0371, + zwave_product_type = 0x0002, + zwave_product_id = 0x0038, +}) + +local mock_co_sensor = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("aeotec-water-sensor-8-co.yml"), + zwave_endpoints = sensor_endpoints, + zwave_manufacturer_id = 0x0371, + zwave_product_type = 0x0002, + zwave_product_id = 0x0038, +}) + +local mock_co2_sensor = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("aeotec-water-sensor-8-co2.yml"), + zwave_endpoints = sensor_endpoints, + zwave_manufacturer_id = 0x0371, + zwave_product_type = 0x0002, + zwave_product_id = 0x0038, +}) + +local mock_contact_sensor = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("aeotec-water-sensor-8-contact.yml"), + zwave_endpoints = sensor_endpoints, + zwave_manufacturer_id = 0x0371, + zwave_product_type = 0x0002, + zwave_product_id = 0x0038, +}) + +local mock_glass_break_sensor = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("aeotec-water-sensor-8-glass-break.yml"), + zwave_endpoints = sensor_endpoints, + zwave_manufacturer_id = 0x0371, + zwave_product_type = 0x0002, + zwave_product_id = 0x0038, +}) + +local mock_motion_sensor = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("aeotec-water-sensor-8-motion.yml"), + zwave_endpoints = sensor_endpoints, + zwave_manufacturer_id = 0x0371, + zwave_product_type = 0x0002, + zwave_product_id = 0x0038, +}) + +local mock_panic_sensor = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("aeotec-water-sensor-8-panic.yml"), + zwave_endpoints = sensor_endpoints, + zwave_manufacturer_id = 0x0371, + zwave_product_type = 0x0002, + zwave_product_id = 0x0038, +}) + +local mock_smoke_sensor = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("aeotec-water-sensor-8-smoke.yml"), + zwave_endpoints = sensor_endpoints, + zwave_manufacturer_id = 0x0371, + zwave_product_type = 0x0002, + zwave_product_id = 0x0038, +}) + +DEVICE_PROFILES = { + [0] = { + profile = "aeotec-water-sensor-8", + mock_device = mock_water_sensor, + default_cap = capabilities.waterSensor.water.dry(), + active_cap = capabilities.waterSensor.water.wet(), + default_cap_str = "dry", + active_cap_str = "wet", + notification_typ = Notification.notification_type.WATER, + on_event = Notification.event.water.LEAK_DETECTED, + off_event = Notification.event.water.STATE_IDLE + }, + [1] = { + profile = "aeotec-water-sensor-8-smoke", + mock_device = mock_smoke_sensor, + default_cap = capabilities.smokeDetector.smoke.clear(), + active_cap = capabilities.smokeDetector.smoke.detected(), + default_cap_str = "clear", + active_cap_str = "detected", + notification_typ = Notification.notification_type.SMOKE, + on_event = Notification.event.smoke.DETECTED, + off_event = Notification.event.smoke.STATE_IDLE + }, + [2] = { + profile = "aeotec-water-sensor-8-co", + mock_device = mock_co_sensor, + default_cap = capabilities.carbonMonoxideDetector.carbonMonoxide.clear(), + active_cap = capabilities.carbonMonoxideDetector.carbonMonoxide.detected(), + default_cap_str = "clear", + active_cap_str = "detected", + notification_typ = Notification.notification_type.CO, + on_event = Notification.event.co.CARBON_MONOXIDE_DETECTED, + off_event = Notification.event.co.STATE_IDLE + }, + [3] = { + profile = "aeotec-water-sensor-8-co2", + mock_device = mock_co2_sensor, + default_cap = capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern.good(), + active_cap = capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern.moderate(), + default_cap_str = "good", + active_cap_str = "moderate", + notification_typ = Notification.notification_type.CO2, + on_event = Notification.event.co2.CARBON_DIOXIDE_DETECTED, + off_event = Notification.event.co2.STATE_IDLE + }, + [4] = { + profile = "aeotec-water-sensor-8-contact", + mock_device = mock_contact_sensor, + default_cap = capabilities.contactSensor.contact.closed(), + active_cap = capabilities.contactSensor.contact.open(), + default_cap_str = "closed", + active_cap_str = "open", + notification_typ = Notification.notification_type.ACCESS_CONTROL, + on_event = Notification.event.access_control.WINDOW_DOOR_IS_OPEN, + off_event = Notification.event.access_control.WINDOW_DOOR_IS_CLOSED + }, + [5] = { + profile = "aeotec-water-sensor-8-contact", + mock_device = mock_contact_sensor, + default_cap = capabilities.contactSensor.contact.closed(), + active_cap = capabilities.contactSensor.contact.open(), + default_cap_str = "closed", + active_cap_str = "open", + notification_typ = Notification.notification_type.ACCESS_CONTROL, + on_event = Notification.event.access_control.WINDOW_DOOR_IS_OPEN, + off_event = Notification.event.access_control.WINDOW_DOOR_IS_CLOSED + }, + [6] = { + profile = "aeotec-water-sensor-8-motion", + mock_device = mock_motion_sensor, + default_cap = capabilities.motionSensor.motion.inactive(), + active_cap = capabilities.motionSensor.motion.active(), + default_cap_str = "inactive", + active_cap_str = "active", + notification_typ = Notification.notification_type.HOME_SECURITY, + on_event = Notification.event.home_security.MOTION_DETECTION, + off_event = Notification.event.home_security.STATE_IDLE + }, + [7] = { + profile = "aeotec-water-sensor-8-glass-break", + mock_device = mock_glass_break_sensor, + default_cap = capabilities.soundDetection.soundDetected.noSound(), + active_cap = capabilities.soundDetection.soundDetected.glassBreaking(), + default_cap_str = "noSound", + active_cap_str = "glassBreaking", + notification_typ = Notification.notification_type.HOME_SECURITY, + on_event = Notification.event.home_security.GLASS_BREAKAGE, + off_event = Notification.event.home_security.STATE_IDLE + }, + [8] = { + profile = "aeotec-water-sensor-8-panic", + mock_device = mock_panic_sensor, + default_cap = capabilities.panicAlarm.panicAlarm.clear(), + active_cap = capabilities.panicAlarm.panicAlarm.panic(), + default_cap_str = "clear", + active_cap_str = "panic", + notification_typ = Notification.notification_type.EMERGENCY, + on_event = Notification.event.emergency.PANIC_ALERT, + off_event = Notification.event.emergency.STATE_IDLE + } +} + +local function test_init() + test.mock_device.add_test_device(mock_water_sensor) + test.mock_device.add_test_device(mock_smoke_sensor) + test.mock_device.add_test_device(mock_co_sensor) + test.mock_device.add_test_device(mock_co2_sensor) + test.mock_device.add_test_device(mock_contact_sensor) + test.mock_device.add_test_device(mock_contact_sensor) + test.mock_device.add_test_device(mock_motion_sensor) + test.mock_device.add_test_device(mock_glass_break_sensor) + test.mock_device.add_test_device(mock_panic_sensor) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Device added lifecycle event for profile", + function() + test.socket.device_lifecycle:__queue_receive({ mock_water_sensor.id, "added" }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_water_sensor, + Configuration:Get({ + parameter_number = 10 + }) + ) + ) + test.socket.capability:__expect_send( + mock_water_sensor:generate_test_message("main", capabilities.moldHealthConcern.supportedMoldValues({"good", "moderate"})) + ) + + test.socket.capability:__expect_send( + mock_water_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) + ) + + test.socket.capability:__expect_send( + mock_water_sensor:generate_test_message("main", capabilities.powerSource.powerSource.battery()) + ) + + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_water_sensor, + Battery:Get({}) + ) + ) + end +) + +test.register_message_test( + "Refresh should generate the correct commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_water_sensor.id, + { capability = "refresh", command = "refresh", args = {} } + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_water_sensor, + Battery:Get({}) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +test.register_message_test( + "Notification report STATE_IDLE event should be handled tamper alert state clear", + { + { + channel = "zwave", + direction = "receive", + message = { mock_water_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.HOME_SECURITY, + event = Notification.event.home_security.STATE_IDLE, + event_parameter = string.char(Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED) + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_water_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + } +) + +test.register_coroutine_test( + "Notification report TAMPERING_PRODUCT_COVER_REMOVED event should be handled as tamperAlert detected", + function() + test.timer.__create_and_queue_test_time_advance_timer(10, "oneshot") + test.socket.zwave:__queue_receive( + { + mock_water_sensor.id, + zw_test_utils.zwave_test_build_receive_command( + Notification:Report( + { + notification_type = Notification.notification_type.HOME_SECURITY, + event = Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED + }) + ) + } + ) + test.socket.capability:__expect_send(mock_water_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.detected())) + end +) + +test.register_message_test( + "Battery report should be handled", + { + { + channel = "zwave", + direction = "receive", + message = { mock_water_sensor.id, zw_test_utils.zwave_test_build_receive_command(Battery:Report({ battery_level = 0x63 })) } + }, + { + channel = "capability", + direction = "send", + message = mock_water_sensor:generate_test_message("main", capabilities.battery.battery(99)) + } + } +) + +test.register_message_test( + "Notification report AC_MAINS_DISCONNECTED event should be handled power source state battery", + { + { + channel = "zwave", + direction = "receive", + message = { mock_water_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.AC_MAINS_DISCONNECTED, + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_water_sensor:generate_test_message("main", capabilities.powerSource.powerSource.battery()) + } + } +) + +test.register_message_test( + "Notification report AC_MAINS_RE_CONNECTED event should be handled power source state dc", + { + { + channel = "zwave", + direction = "receive", + message = { mock_water_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.AC_MAINS_RE_CONNECTED, + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_water_sensor:generate_test_message("main", capabilities.powerSource.powerSource.dc()) + } + } +) + +test.register_message_test( + "Notification report POWER_HAS_BEEN_APPLIED event should be send battery get", + { + { + channel = "zwave", + direction = "receive", + message = { mock_water_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.POWER_HAS_BEEN_APPLIED, + })) } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_water_sensor, + Battery:Get({}) + ) + } + } +) + +test.register_message_test( + "Notification report LEAK_DETECTED event should be handled water sensor state wet", + { + { + channel = "zwave", + direction = "receive", + message = { mock_water_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.WATER, + event = Notification.event.water.LEAK_DETECTED, + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_water_sensor:generate_test_message("main", capabilities.waterSensor.water.wet()) + } + } +) + +test.register_message_test( + "Notification report STATE_IDLE event should be handled water sensor state dry", + { + { + channel = "zwave", + direction = "receive", + message = { mock_water_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.WATER, + event = Notification.event.water.STATE_IDLE, + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_water_sensor:generate_test_message("main", capabilities.waterSensor.water.dry()) + } + } +) + + +for param_value, data in pairs(DEVICE_PROFILES) do + local value = param_value + local profile = data.profile + local mock_device = data.mock_device + local default_cap = data.default_cap + local active_cap = data.active_cap + local notification_type = data.notification_typ + local on_event = data.on_event + local off_event = data.off_event + + + test.register_coroutine_test( + "Profile should update when Configuration Report parameter 10 = " .. value, + function() + test.socket.zwave:__queue_receive({ + mock_device.id, + Configuration:Report({ + parameter_number = 10, + configuration_value = value + }) + }) + + test.socket.device_lifecycle:__expect_send( + mock_device:expect_metadata_update({ + profile = profile + }) + ) + + if profile == "aeotec-water-sensor-8-glass-break" then + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.soundDetection.supportedSoundTypes({"noSound", "glassBreaking"})) + ) + elseif profile == "aeotec-water-sensor-8-co2" then + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"good", "moderate"})) + ) + end + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", default_cap) + ) + end + ) + + test.register_coroutine_test( + "Notification report type " .. notification_type .. " event ".. off_event .. " should be handled " .. data.default_cap_str, + function() + mock_device:set_field("active_profile", profile) + + test.socket.zwave:__queue_receive({ + mock_device.id, + Notification:Report({ + notification_type = notification_type, + event = off_event, + event_parameter = string.char(on_event) + }) + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", default_cap) + ) + end + ) + + test.register_coroutine_test( + "Notification report type " .. notification_type .. " event ".. on_event .. " should be handled " .. data.active_cap_str, + function() + mock_device:set_field("active_profile", profile) + + test.socket.zwave:__queue_receive({ + mock_device.id, + Notification:Report({ + notification_type = notification_type, + event = on_event, + }) + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", active_cap) + ) + end + ) +end + +test.run_registered_tests() \ No newline at end of file From e9512fd1e8a00318c55c744cc69d88e378fb8d77 Mon Sep 17 00:00:00 2001 From: mh-zwave Date: Thu, 26 Mar 2026 13:32:32 +0100 Subject: [PATCH 06/10] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 7e084fdbf892c008298c2c8b2cbd48d71c637023 Author: mh-zwave Date: Tue Mar 17 13:19:25 2026 +0100 Update init.lua commit 73b6bc0208b8623a3f3a9d494be266783e70e2bc Author: mh-zwave Date: Tue Mar 17 13:17:55 2026 +0100 Update init.lua commit a86d90a732cd785cc1ed6df04071e4d255137fea Author: mh-zwave Date: Tue Mar 17 13:16:38 2026 +0100 Update init.lua commit bd322d6f5db734b5f54897749e4e1ff2c3838789 Author: mh-zwave Date: Tue Mar 17 13:08:11 2026 +0100 improve excluded_devices ID matching logic commit 3c15a0b6f48b5bbe2d3e460c50bdbdb82f745258 Merge: 5e7333ac da4e8ea5 Author: mh-zwave Date: Wed Jan 28 15:34:31 2026 +0100 Merge branch 'main' into feature/timed-tamper-clear-multi-vendor commit da4e8ea5f5b00c36e8bfc980dffc9ee0bd1980e2 Author: Marcin Krystian Tyminski <81477027+marcintyminski@users.noreply.github.com> Date: Tue Jan 27 20:41:58 2026 +0100 WWSTCERT-9438 Add frient io module driver (#2600) * initial commit * test changes * add register_native_switch_handler * tests WIP * working tests * get rid of unused variables * test * additional test * Revert changes and add can_handle file * fixed test * capabilities fix * Changes according to pr comments * changes according to pr comments * Shortened Copyright statement commit 52ab8d347b684159d8b03339c7c6fe31ac4f9985 Author: Jeff Page Date: Tue Jan 27 13:38:11 2026 -0600 WWSTCERT-9861 Add Zooz ZSE42 to zwave-sensor (for WWST Cert) (#2683) * Add ZSE42 to zwave-sensor * Added sub_driver to handle populating firmewareUpdate.currentVersion * Added ability to set battery quantity and type in configurations * zwave-sensor requested changes * Moved Version:Get from init to added * Added unit test as requested commit 5e7750c4ebc02fbf69a2eaedccc20de467aa50db Merge: 6febfb86 37d34537 Author: Alec Lorimer Date: Mon Jan 26 15:12:10 2026 -0600 Merge pull request #2647 from SmartThingsCommunity/zwave-siren-lazy-load-subdrivers CHAD-17093: zwave-siren lazy loading of sub-drivers commit 6febfb8618b68fb8afa7c0abc738c99916b0dd2d Merge: 13238e79 7235ce7b Author: Alec Lorimer Date: Mon Jan 26 15:11:48 2026 -0600 Merge pull request #2636 from SmartThingsCommunity/zigbee-watering-kit-lazy-load-subdrivers CHAD-17081: zigbee-watering-kit lazy loading of sub-drivers commit 13238e79bab61a72d4c4d5d0a913234894fd554b Merge: 001f6f2f 9278b2bc Author: Alec Lorimer Date: Mon Jan 26 15:11:27 2026 -0600 Merge pull request #2639 from SmartThingsCommunity/zwave-bulb-lazy-load-subdrivers CHAD-17084: zwave-bulb lazy loading of sub-drivers commit 001f6f2fe3e69b6d952027223b6105eca4e01e6d Merge: c55379e8 7d48d6c0 Author: Alec Lorimer Date: Mon Jan 26 15:11:01 2026 -0600 Merge pull request #2637 from SmartThingsCommunity/zigbee-water-leak-sensor-lazy-load-subdrivers CHAD-17082: zigbee-water-leak-sensor lazy loading of sub-drivers commit c55379e85bc4a3a2fcc1bbedcb574e9125b567ee Merge: 58b7e07a 3dde32de Author: Steven Green Date: Mon Jan 26 12:30:32 2026 -0800 Merge pull request #2734 from SmartThingsCommunity/fixup/orein-fan fixup Orein fan profile commit 3dde32dece000274558d78032f7797348d375619 Author: Steven Green Date: Mon Jan 26 12:14:27 2026 -0800 fixup Orein fan profile commit 58b7e07ad733d532a17bdd52da6530bf4d18344f Merge: 8b56e698 c4a69144 Author: Steven Green Date: Mon Jan 26 11:56:49 2026 -0800 Merge pull request #2726 from SmartThingsCommunity/new_device/WWSTCERT-9989 WWSTCERT-9989 Linkind Smart Light Bulb commit 8b56e69856cd0ef52161b3c1c00d418faf63b548 Merge: 201887c1 31796736 Author: Steven Green Date: Mon Jan 26 10:11:41 2026 -0800 Merge pull request #2732 from SmartThingsCommunity/revert/aqara_t1_update Revert "Merge pull request #2662 from haedo-doo/Aqara-DimmerControlle… commit 201887c11e3b59e2eb8fffcd7d1d8111957e7d1d Merge: b356ab85 6ce8ae63 Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Mon Jan 26 11:55:35 2026 -0600 Merge pull request #2727 from SmartThingsCommunity/fix/matter-switch-refresh Fix matter switch refresh capability command commit 6ce8ae631da2d15bb16c78ec6f3de6f77ef41304 Author: Nick DeBoom Date: Fri Jan 23 10:01:21 2026 -0600 Fix matter switch refresh capability command commit 3179673679e400e1528823c00024f70991c2b3ab Author: Steven Green Date: Mon Jan 26 09:49:46 2026 -0800 Revert "Merge pull request #2662 from haedo-doo/Aqara-DimmerControllerT1-remove-unused-code" This reverts commit 3444bc31f35e684269966915912b42c9065fa78c, reversing changes made to 37f8536890432fa51645ab9dff3aa2be45565744. commit b356ab85eccaac382f76abcd40763d932fd03149 Merge: 6a7fe71f 1646987d Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Mon Jan 26 11:35:50 2026 -0600 Merge pull request #2728 from SmartThingsCommunity/add/ledvance-vendor-override Add vendor override for Ledvance commit 6a7fe71f6b17d596b0d297d50b867aa32bcbec3b Merge: 6626e2e3 32f481e4 Author: Alec Lorimer Date: Mon Jan 26 11:27:31 2026 -0600 Merge pull request #2620 from SmartThingsCommunity/zigbee-dimmer-remote-lazy-load-subdrivers CHAD-17066: zigbee-dimmer-remote lazy loading subdrivers commit 6626e2e360f6b179bafba48296aab4087985e55b Merge: de5ab7a4 57fe1948 Author: Alec Lorimer Date: Mon Jan 26 11:24:57 2026 -0600 Merge pull request #2618 from SmartThingsCommunity/zigbee-carbon-monoxide-detector-lazy-load-subdrivers CHAD-17046: zigbee-carbon-monoxide-detector subdriver lazy loading commit 32f481e40969b011d872b996be3942f32b6747db Author: Alec Lorimer Date: Mon Nov 17 15:25:20 2025 -0600 CHAD-17066: zigbee-dimmer-remote lazy loading subdrivers commit 57fe19489fb2fd00faad6a7834c4aad8f4cf8ea6 Author: Alec Lorimer Date: Mon Nov 17 15:25:42 2025 -0600 CHAD-17046: zigbee-carbon-monoxide-detector subdriver lazy loading commit de5ab7a45dfece98c8d68b560c999ac012ce26c0 Merge: d908a435 5da406dc Author: Alec Lorimer Date: Mon Jan 26 11:16:40 2026 -0600 Merge pull request #2666 from SmartThingsCommunity/CHAD-17152-matter-appliance-lazy-loading-subdrivers CHAD-17152: Lazy loading of matter-appliance sub-drivers commit 1646987d4a9d12ceebf516575940a0e2a138f4fc Author: Nick DeBoom Date: Mon Jan 26 10:18:09 2026 -0600 allow override for non-plugs commit d908a4353cdcad197e9a0e5dc32a2c2adf4b42e1 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon Jan 26 09:14:12 2026 -0600 Matter Thermostat: Set supportedThermostatOperatingStates if applicable (#2723) commit c8f0de05162046491ba4625e6383046979e331d1 Author: Nick DeBoom Date: Fri Jan 23 16:08:22 2026 -0600 Add vendor override for Ledvance commit c4a69144c153c68a4148146bdc23762f6a3ff947 Author: Steven Green Date: Wed Jan 21 15:56:24 2026 -0800 WWSTCERT-9989 Linkind Smart Light Bulb commit 9d77d58f9bf8c3ac0909608adb41dfe2c4bef189 Merge: 6d0237db 3cae7e15 Author: Steven Green Date: Fri Jan 23 12:19:58 2026 -0800 Merge pull request #2711 from SmartThingsCommunity/new_device/WWSTCERT-9960 WWSTCERT-9960 HooRii Window Covering commit 6d0237dba0b1d6148e5094f11cd0dd8144350f61 Author: Steven Green Date: Wed Jan 21 16:00:32 2026 -0800 WWSTCERT-9895 WWSTCERT-9936 OREiN Matter smart Bathroom Fan (#2705) * WWSTCERT-9895 OREiN Matter smart Bathroom Fan * WWSTCERT-9936 OREiN Matter smart Bathroom Fan * profile updated to modular commit 8bdbadb0f8976cfc7cf01344cef6c509044b0bd7 Merge: f510d1d8 a218cda8 Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Wed Jan 21 15:59:10 2026 -0600 Merge pull request #2710 from SmartThingsCommunity/run-driver-tests-script-add-html-output-option run_driver_tests.py: add option to output html coverage reports commit f510d1d8d73394c163c3aaf6a1a8a16eacf4517b Merge: 2192441d 91068085 Author: NoahCornell <67164901+NoahCornell@users.noreply.github.com> Date: Wed Jan 21 13:31:33 2026 -0600 Merge pull request #2721 from SmartThingsCommunity/fix_sonos_upserts CHAD-17218: Fix Sonos augmented driver store upserts commit 91068085afbc5510925d0ac7d3fc1c73f02730e0 Author: NoahCornell Date: Tue Jan 20 16:52:42 2026 -0600 CHAD-17218: Fix Sonos augmented driver store upserts This was broken in hubcore 59 due to IPC rework. commit 2192441df053cb9bd25e96c81e50e84f17c053dc Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Tue Jan 20 13:27:16 2026 -0600 Revert "Exclude off from fanMode capability for Thermostats (#2673)" (#2715) This reverts commit b8b70686af6bfb80cc351a31c5e6d59653a88d72. commit e57dc95159c3c3103e14804a40accf96bb876d10 Merge: 245785a4 9ba659d0 Author: Steven Green Date: Fri Jan 16 15:39:14 2026 -0800 Merge pull request #2606 from SmartThingsCommunity/new_device/WWSTCERT-9293 WWSTCERT-9293 eufy FamiLock E40 commit 245785a4e097eff8189ebe80974d2a7c90463174 Merge: db04ee80 15b4d7bc Author: Steven Green Date: Fri Jan 16 15:37:45 2026 -0800 Merge pull request #2704 from SmartThingsCommunity/new_device/WWSTCERT-9840 WWSTCERT-9840 Aqara U200 US commit db04ee8022d2d52fdfb3e3f0762f4a5d5c92769e Merge: 17c337ed 40c93468 Author: Steven Green Date: Fri Jan 16 15:32:02 2026 -0800 WWSTCERT-9799 Eve Thermostat (Europe) (#2703) commit 15b4d7bce5f234a3015533f7c1c90e1a59221320 Author: Steven Green Date: Tue Jan 13 16:15:34 2026 -0800 WWSTCERT-9840 Aqara U200 US commit 17c337edc7d0e52cb78edc78edeb2310a1eb7bd9 Author: Steven Green Date: Fri Jan 16 15:25:30 2026 -0800 Osram switches (#2697) * Osram switches WWSTCERT-9564 WWSTCERT-9561 WWSTCERT-9558 WWSTCERT-9555 WWSTCERT-9552 WWSTCERT-9549 WWSTCERT-9546 WWSTCERT-9543 WWSTCERT-9540 WWSTCERT-9537 WWSTCERT-9534 WWSTCERT-9531 WWSTCERT-9528 WWSTCERT-9525 WWSTCERT-9522 WWSTCERT-9519 WWSTCERT-9516 WWSTCERT-9513 WWSTCERT-9510 WWSTCERT-9507 WWSTCERT-9504 WWSTCERT-9501 * WWSTCERT-9579 WWSTCERT-9576 WWSTCERT-9573 WWSTCERT-9570 WWSTCERT-9567 * WWSTCERT-9779 WWSTCERT-9776 WWSTCERT-9773 WWSTCERT-9770 WWSTCERT-9767 WWSTCERT-9764 WWSTCERT-9761 WWSTCERT-9758 WWSTCERT-9755 WWSTCERT-9752 WWSTCERT-9749 WWSTCERT-9746 WWSTCERT-9743 WWSTCERT-9740 WWSTCERT-9737 WWSTCERT-9734 WWSTCERT-9731 WWSTCERT-9728 WWSTCERT-9725 WWSTCERT-9722 WWSTCERT-9719 WWSTCERT-9716 WWSTCERT-9713 WWSTCERT-9710 WWSTCERT-9707 WWSTCERT-9704 WWSTCERT-9701 WWSTCERT-9698 WWSTCERT-9695 WWSTCERT-9692 WWSTCERT-9689 WWSTCERT-9686 WWSTCERT-9683 WWSTCERT-9680 WWSTCERT-9677 WWSTCERT-9674 WWSTCERT-9671 WWSTCERT-9668 WWSTCERT-9665 WWSTCERT-9662 WWSTCERT-9659 WWSTCERT-9656 WWSTCERT-9653 WWSTCERT-9650 WWSTCERT-9647 WWSTCERT-9644 WWSTCERT-9641 WWSTCERT-9638 WWSTCERT-9635 WWSTCERT-9632 WWSTCERT-9629 WWSTCERT-9626 WWSTCERT-9623 WWSTCERT-9620 commit ca43f2f6a1df4e8f17df6a613dff930764a31210 Merge: d5d5c47f 05cce9cc Author: Steven Green Date: Fri Jan 16 15:00:29 2026 -0800 Merge pull request #2656 from inasail/bugfix/CHAD-17017 CHAD-17017: Add the ReadAttrbitue or refresh code for the initial state commit d5d5c47f2da16374b43d41ade0d07d872a770298 Merge: 98d2c30d 689a6a50 Author: Steven Green Date: Fri Jan 16 14:51:15 2026 -0800 Merge pull request #2699 from SmartThingsCommunity/new_device/WWSTCERT-9605 WWSTCERT-9605 Kwikset Aura Reach commit 98d2c30d4b0b24858ffaec46cc17fb4c2627f084 Merge: e0805baf cdf4d494 Author: Steven Green Date: Fri Jan 16 14:49:22 2026 -0800 Merge pull request #2698 from SmartThingsCommunity/new_device/WWSTCERT-9480 WWSTCERT-9480 Sensereo MSC-1 commit e0805bafd3271ced84f5d5ef4470cd8485c67ede Author: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Fri Jan 16 10:49:26 2026 +0900 Add vid/pid of the Aqara U400 to new-matter-lock (#2709) Signed-off-by: Hunsup Jung commit 3cae7e15e2972411289e09c60a8fe64422163b9a Author: Steven Green Date: Thu Jan 15 13:43:28 2026 -0800 WWSTCERT-9960 HooRii Window Covering commit 9ba659d064eef34bcd87a23cc9fab2d63ed5f09a Author: Steven Green Date: Fri Dec 5 11:44:03 2025 -0800 WWSTCERT-9289 eufy FamiLock E40 commit a218cda82359844bee48039fa908b69f4eeef58e Author: Nick DeBoom Date: Thu Jan 15 14:30:47 2026 -0600 run_driver_tests.py: add option to output html coverage report This adds an optional argument to the run_driver_tests.py script, which when passed generates HTML coverage reports for the files specified by the --coverage argument. These are handy for opening up in a browser or previewing in an IDE to help spot gaps in line coverage. commit 6ddbbad5508d2c41beb7e1964bb5f7bb95478b81 Merge: 3444bc31 8841dcac Author: Abdul Samad Date: Thu Jan 15 10:03:59 2026 -0600 Merge pull request #2708 from SmartThingsCommunity/matter-camera-add-vision-clipAnalysis-capability Matter Camera: Add vision.clipAnalysis capability commit 5e7333ac3917ebfc4ceeb23aebed32be58874777 Author: mh-zwave Date: Thu Jan 15 09:24:57 2026 +0100 Update init.lua commit 04166e574d933edf18ccf1e7a74f150169887f7a Author: mh-zwave Date: Thu Jan 15 09:23:10 2026 +0100 remove trailing whitespace commit fb02cf6ad3fd411f1f1a5460620490a1ce7134ee Author: mh-zwave Date: Thu Jan 15 09:18:18 2026 +0100 fix(tamper-handler): correct exclusion logic and refactor for multi-vendor support commit 8841dcaced89c52b06fd84d20a9cfd51c58d9fca Author: Nick DeBoom Date: Wed Jan 14 15:05:52 2026 -0600 Matter Camera: Add vision.clipAnalysis capability Add vision.clipAnalysis to the Matter Camera profile. commit 5d912f655d3c107f1aebb3ed8e70e9300285b250 Author: mh-zwave Date: Wed Jan 14 14:58:20 2026 +0100 feat(tamper): add multi-device exclusion via id_match loop commit 05cce9cc7f3829677e93bee14faceac41674ca9c Author: Taejun Park Date: Mon Dec 15 20:33:41 2025 +0900 Add the ReadAttrbitue or refresh code for the initial state commit 3444bc31f35e684269966915912b42c9065fa78c Merge: 37f85368 cba8c07d Author: Steven Green Date: Tue Jan 13 16:28:53 2026 -0800 Merge pull request #2662 from haedo-doo/Aqara-DimmerControllerT1-remove-unused-code [Aqara/Dimmer Controller T1] Remove unused code(follow-up PR) commit 37f8536890432fa51645ab9dff3aa2be45565744 Merge: 1d911f95 f4299ed9 Author: Steven Green Date: Tue Jan 13 15:23:25 2026 -0800 Merge pull request #2655 from SmartThingsCommunity/new_device/WWSTCERT-9475 WWSTCERT-9475 eWeLink Zigbee Motion Sensor commit 40c934686454de1a2376e8af40ad95c6e186a988 Author: Steven Green Date: Tue Jan 13 14:57:41 2026 -0800 WWSTCERT-9799 Eve Thermostat (Europe) commit 689a6a50f9802ade090bf4030666c7a75ed6a5e3 Author: Steven Green Date: Mon Jan 12 14:59:35 2026 -0800 WWSTCERT-9605 Kwikset Aura Reach commit cdf4d494305f9c6a569457c367bc1abb1509aa7b Author: Steven Green Date: Mon Jan 12 14:48:40 2026 -0800 WWSTCERT-9480 Sensereo MSC-1 commit 1d911f954e93765a76ad1cdff00f8146539c5891 Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Mon Jan 12 14:34:50 2026 -0600 Matter Camera: Only update profile if capabilities have changed (#2686) The `optional_capabilities_list_changed` function was not properly comparing the new set of supported capabilities with the current set, meaning that a profile update was occurring each time `camera_av_stream_management_attribute_list_handler` runs. This commit fixes `optional_capabilities_list_changed` and provides new test cases to ensure that the extraneous profile updates no longer occur. commit 409944a1271440264d27f8ce0c690c38cc9c9a8c Merge: 4058704d 1251d573 Author: Alec Lorimer Date: Mon Jan 12 11:29:27 2026 -0600 Merge pull request #2651 from SmartThingsCommunity/zwave-window-treatment-lazy-load-subdrivers CHAD-17098: zwave-window-treatment lazy loading of sub-drivers commit 4058704da99888637a786e6f9b7a540f05d4c53d Merge: 6b5515dd 440180b7 Author: Alec Lorimer Date: Mon Jan 12 11:28:49 2026 -0600 Merge pull request #2641 from SmartThingsCommunity/zwave-electric-meter-lazy-load-subdrivers CHAD-17086: zwave-electric-meter lazy loading of sub-drivers commit 6b5515dd14a02c6759f4c21a860cd96568e4466c Merge: fb1b858e 1224494b Author: Alec Lorimer Date: Mon Jan 12 11:18:58 2026 -0600 Merge pull request #2625 from SmartThingsCommunity/zigbee-presence-sensor-lazy-load-subdrivers CHAD-17071: zigbee-presence-sensor lazy loading of subdrivers commit fb1b858e22eceaf6643274945ee7d1b52c4de091 Merge: c73e38b4 8422dd5f Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon Jan 12 11:01:36 2026 -0600 Merge pull request #2694 from SmartThingsCommunity/remove-unused-helper commit 8422dd5f5fd2fef9f854d4a9b1c1fcf93f8300fa Author: Harrison Carter Date: Mon Jan 12 10:51:01 2026 -0600 remove unused helper function, logic moved to generic child handler commit c73e38b4fe1bae06716b25e2e6ac105a6263a819 Merge: 2a377a1d 3de35fd2 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon Jan 12 10:35:56 2026 -0600 Merge pull request #2667 from SmartThingsCommunity/update/matter-switch-subscribe-logic-no-fields commit 3de35fd2f8cddbfade10a58d61fe38e41c9d2659 Author: Harrison Carter Date: Sat Dec 13 22:00:05 2025 -0600 update subscribe for main driver and camera subdriver commit 2a377a1dcc47c62e2f5ed2db257681c10e23cff0 Merge: b4b23f0c 05aa526e Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Fri Jan 9 12:01:25 2026 -0600 Merge pull request #2692 from SmartThingsCommunity/handle-electrical-in-switched Matter Switch: Add electrical field settings to driverSwitched commit b4b23f0c1b3ea15de9ab1537a1b853cc7e799ae8 Merge: fb951eee 992bc3f1 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Fri Jan 9 11:30:05 2026 -0600 Merge pull request #2690 from SmartThingsCommunity/update-level-capability-handler commit 05aa526eedbf28c42d25e146a986850ca489b571 Author: Harrison Carter Date: Fri Jan 9 11:26:18 2026 -0600 add electrical handling to driverSwitched commit fb951eee3808f42866cc7f8af6e43efe97d094a6 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu Jan 8 16:56:24 2026 -0600 Matter Switch: Support fan/light devices as parent/child (#2653) commit c795f829a9109831c261ee8ca4ac8dd189bf3366 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu Jan 8 16:40:59 2026 -0600 Matter Switch: Attempt re-profiling on device software updates (#2466) * re-profile device if a matter_version update occurs commit 992bc3f1db4a2e49772be7e32a427f47d9b203db Author: Harrison Carter Date: Thu Jan 8 16:04:04 2026 -0600 update floor to round in level cap handler commit b8b70686af6bfb80cc351a31c5e6d59653a88d72 Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Thu Jan 8 15:56:06 2026 -0600 Exclude off from fanMode capability for Thermostats (#2673) Exclude the off mode from the supportedFanModes attribute of the fanMode capability for Thermostats. commit 1251d573ea0049bee5c757bd67213add78976b96 Author: Alec Lorimer Date: Thu Jan 8 15:41:20 2026 -0600 fixup: Fixed utf-8 errors in zigbee-motion-sensor tests commit 50e0092e2267a5e94010e6bb836c20631d6140b4 Author: Alec Lorimer Date: Mon Nov 17 15:25:22 2025 -0600 CHAD-17098: zwave-window-treatment lazy loading of sub-drivers commit 9b50583a2c227c5f4672c0c525539ed1307300f8 Merge: 7e3e6df4 2d69ed87 Author: Alec Lorimer Date: Thu Jan 8 15:36:35 2026 -0600 Merge pull request #2622 from SmartThingsCommunity/zigbee-humidity-sensor-lazy-load-subdrivers CHAD-17068: zigbee-humidity-sensor lazy load sub drivers commit 2d69ed871b82bc9c3e7e6fab73a30d56ee4e3a58 Author: Alec Lorimer Date: Mon Nov 17 15:25:31 2025 -0600 CHAD-17068: zigbee-humidity-sensor lazy load sub drivers commit 7e3e6df4c6eee04cba293678a2e4aa177d87b094 Merge: bddd262c 6ba6ebf6 Author: Alec Lorimer Date: Thu Jan 8 13:46:02 2026 -0600 Merge pull request #2664 from SmartThingsCommunity/CHAD-17161-matter-sensor-lazy-loading-subdrivers CHAD-17161: matter-sensor: lazy loading of sub-drivers commit bddd262ccd9c591b50a51c9826fcffab9a56c51f Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Thu Jan 8 10:33:34 2026 -0600 Matter Camera: fix devices being marked online due to capability inits (#2688) The driverSwitched event can be triggered in a few different ways, but since it was pointed to the same handler as doConfigure, code that should only be ran for new devices was being run when this event was received by the driver. This includes `initialize_camera_capabilities`, which resulted in offline devices being marked online. This change adds separate handling from driverSwitched, specifically excluding this function as well as `create_child_devices`. commit 6ba6ebf6e9fc748d2ea35827b58c23592af4037d Merge: f89ad93d 0e13a711 Author: Alec Lorimer Date: Wed Jan 7 16:41:45 2026 -0600 Merge branch 'main' into CHAD-17161-matter-sensor-lazy-loading-subdrivers commit 0e13a711c5beb9a9b8e4f5c2d0d1131597b2f8fc Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Wed Jan 7 13:33:17 2026 -0600 Gate videoCapture2 based on PushAvStreamTransport (#2680) The VIDEO feature of CameraAvStreamManagement can be enabled on live-view only cameras, so the videoCapture2 capability should also be gated based on the presence of the PushAvStreamTransport clusters (presence of which also implies the presence of TLS clusters). commit 9e159e3de19f6820f4c84e3d26aedfe6c89233dd Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed Jan 7 10:40:08 2026 -0600 Matter Switch: Register native handlers for Hue and Saturation (#2687) * register native handlers for hue and saturation commit 1defafc592cc5117350125710ff3ee897d97c15a Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Tue Jan 6 15:29:44 2026 -0600 Matter Thermostat: Support fanSpeedPercent (#2670) Support the fanSpeedPercent capability for thermostats based on the presence of the MULTI_SPEED feature. commit aa630fa655fb4f7827570d8bb414662c22dbd987 Merge: 9e92a6fd 7b099ca8 Author: Abdul Samad Date: Thu Dec 18 14:18:28 2025 -0600 Merge pull request #2674 from SmartThingsCommunity/matter-camera/motion-sensor-condition Add motionSensor condition, and imageCapture action commit 9e92a6fdcfd88b9a7b3deaa3205a1ba322db5464 Merge: 25bef140 5da560c7 Author: Zach Varberg Date: Thu Dec 18 10:34:04 2025 -0600 Merge pull request #2125 from AldaronLau/better-match-default-handlers-behavior Matter switch and zigbee fan switchLevel: Match default handlers behavior around small values commit 7b099ca887eeab53712c32acadc2f725862f5b5d Author: Abdul Samad Date: Thu Dec 18 10:09:37 2025 -0600 Add motionSensor condition, and imageCapture action commit 25bef1408d489174593a72f667379bb7fa6faf90 Merge: 530fbc56 377e001c Author: Cooper Towns Date: Thu Dec 18 09:34:48 2025 -0600 Merge pull request #2671 from SmartThingsCommunity/matter-camera/video-capture-automation [Matter Camera] Add videoCapture2 action to embedded device config commit 5da560c7b729c99bde965282b4edb6788a5144f8 Author: Jeron Aldaron Lau Date: Tue May 13 17:22:17 2025 -0500 Fix zigbee fan switch level behavior for small values commit b52b3c082f55739bf94e88877b4ba5fa2f5ee214 Author: Jeron Aldaron Lau Date: Tue May 13 17:20:22 2025 -0500 Fix matter switch level behavior for small values commit 530fbc560abda5ed3a967563465d56f9d0d92ae5 Merge: 4524087f d7d54ddb Author: Zach Varberg Date: Wed Dec 17 09:43:56 2025 -0600 Merge pull request #2126 from AldaronLau/update-code-formatting-criteria-broken-link Fix broken link for contributing code guidelines commit 377e001c26e560874f345c4b53958b6cbe173736 Author: Cooper Towns Date: Tue Dec 16 17:09:15 2025 -0600 [Matter Camera] Add videoCapture2 action to embedded device config commit 5da406dc72153d4fe05ccce92ba99faa4cfe59b7 Author: Alec Lorimer Date: Mon Dec 15 15:22:12 2025 -0600 CHAD-17152: Lazy loading of matter-appliance sub-drivers commit f89ad93d9518fff574d4dd5d8c3151620d50a6c1 Author: Alec Lorimer Date: Tue Dec 16 09:35:21 2025 -0600 CHAD-17161: lazy loading of matter-sensor sub-drivers commit cba8c07d0a530d1f877a57b1f1d35cc673513d79 Author: haedo-doo Date: Tue Dec 16 10:30:14 2025 +0900 a follow-up PR for #2468 that removes the no longer needed code from init commit 4524087fb0501c8eaf01f10b8d0a37583ac37eb7 Merge: bb70244e 24234be6 Author: Steven Green Date: Mon Dec 15 10:36:11 2025 -0800 adjust base profile for ikea bilresa scroll (#2657) commit 24234be689f4434beb7f56765df277a41ed02d74 Author: Steven Green Date: Mon Dec 15 10:15:45 2025 -0800 adjust base profile for ikea bilresa scroll commit bb70244e0cf98fab138df6bbe261895ceedd2610 Author: Script0803 <116477970+script0803@users.noreply.github.com> Date: Sun Dec 14 04:35:22 2025 +0800 Merge pull request #1999 from script0803/main WWSTCERT-8208 Add new Zigbee Power Meter Device commit f4299ed95d0db938e82b61de6aefd4a03f21ad3d Author: Steven Green Date: Sat Dec 13 11:53:28 2025 -0800 WWSTCERT-9475 eWeLink Zigbee Motion Sensor commit ec6f6e251eb99a96363c302fde85b03dbc5dc0fd Merge: 5a96c155 f9b30eea Author: Steven Green Date: Fri Dec 12 14:15:18 2025 -0800 Merge pull request #2652 from SmartThingsCommunity/fixup/govee_duplicates comment out govee duplicate fingerprints commit f9b30eea8456f0c324efb2cde1864b8c102e8ae2 Author: Steven Green Date: Fri Dec 12 14:10:21 2025 -0800 comment out govee duplicate fingerprints commit 37d3453784d695b9d51e846aec29724a0dedfb23 Author: Alec Lorimer Date: Mon Nov 17 15:25:39 2025 -0600 CHAD-17093: zwave-siren lazy loading of sub-drivers commit 7d48d6c094aab90868ea1e16ff175e37a05b663d Author: Alec Lorimer Date: Mon Nov 17 15:25:10 2025 -0600 CHAD-17082: zigbee-water-leak-sensor lazy loading of sub-drivers commit 5a96c155f0f64523f8735722dee5cd8204c3b99f Merge: ad0923ff 1c8e9319 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Fri Dec 12 13:35:48 2025 -0600 Merge pull request #2634 from SmartThingsCommunity/add/initialpress-to-scroll-subscription add InitialPress to Ikea Scroll subscription commit ad0923ffe58d17d984d83fd6cd8485e02be437b6 Merge: 52c9490c ed4e8cd4 Author: Alec Lorimer Date: Fri Dec 12 13:21:36 2025 -0600 Merge pull request #2601 from SmartThingsCommunity/CHAD-17043-pre-commit-newline-copyright-checks CHAD-17043: Created pre-commit script to check for newline on all lua files, and copyright on all SmartThings lua driver files. commit 1c8e9319882cfa75a60be1c7f9aecff5ede5d86c Author: Harrison Carter Date: Fri Dec 12 13:17:04 2025 -0600 add InitialPress to Ikea Scroll subscription commit 52c9490c8305a34198675b20a66055770cd41952 Merge: 2a14c82c 416fdd35 Author: Steven Green Date: Fri Dec 12 10:09:44 2025 -0800 Merge pull request #2632 from seojune79/Aqara-WirelessRemoteSwitch-BatteryInfo [Aqara/Wireless Remote Switch] Correcting Incorrect Battery Information commit 416fdd3556153c0e60e00c66f6e1235b6a9e1d15 Author: seojune Date: Fri Dec 12 10:58:30 2025 +0900 Correcting Incorrect Battery Information commit 2a14c82c1900bf2bf68780c0b7d3e02eb4677586 Author: akangyou <15802427350@163.com> Date: Fri Dec 12 06:08:48 2025 +0800 add Yanmi fingerprints and cn.csv (#2599) commit 8ca4d31fc3ae57af0fbb94316bfcadc9b09f0c28 Author: Steven Green Date: Thu Dec 11 13:21:47 2025 -0800 Govee devices (#2631) commit fb7feeeaa473ca5c53348f21907227f660975630 Author: Steven Green Date: Thu Dec 11 13:21:11 2025 -0800 Lifx devices (#2584) * Lifx devices WWSTCERT-8588 WWSTCERT-8609 WWSTCERT-8597 WWSTCERT-8498 WWSTCERT-8594 WWSTCERT-8612 WWSTCERT-8591 WWSTCERT-8519 WWSTCERT-8540 WWSTCERT-8561 WWSTCERT-8486 WWSTCERT-8576 WWSTCERT-8537 WWSTCERT-8516 WWSTCERT-8558 WWSTCERT-8579 WWSTCERT-8573 WWSTCERT-8552 WWSTCERT-8534 WWSTCERT-8504 WWSTCERT-8582 WWSTCERT-8492 WWSTCERT-8606 WWSTCERT-8585 WWSTCERT-8507 WWSTCERT-8495 WWSTCERT-8513 WWSTCERT-8570 WWSTCERT-8489 WWSTCERT-8549 WWSTCERT-8531 WWSTCERT-8510 WWSTCERT-8567 WWSTCERT-8525 WWSTCERT-8528 WWSTCERT-8600 WWSTCERT-8546 WWSTCERT-8543 WWSTCERT-8564 WWSTCERT-8555 * remove duplicate * remove duplicate * remove duplicates * formatting * formatting commit df42e76c562c927b58269e2a81759955f39bee05 Merge: 55ad81c8 c9b317e6 Author: Steven Green Date: Thu Dec 11 13:16:54 2025 -0800 Merge pull request #2612 from SmartThingsCommunity/new_device/ikea_sensors WWSTCERT-9343 commit 55ad81c8419d533da0c327c2b776dad4ac29fcd3 Merge: 23f30268 6f354e73 Author: Steven Green Date: Thu Dec 11 13:16:43 2025 -0800 Merge pull request #2613 from SmartThingsCommunity/new_device/WWSTCERT-9352 WWSTCERT-9352 Ikea Bilresa commit 440180b7a61b3f87dfc4ac68421effbe7c4ac358 Author: Alec Lorimer Date: Mon Nov 17 15:25:15 2025 -0600 CHAD-17086: zwave-electric-meter lazy loading of sub-drivers commit 9278b2bc4427fd886967ab0feda1f93714ab9e61 Author: Alec Lorimer Date: Mon Nov 17 15:25:38 2025 -0600 CHAD-17084: zwave-bulb lazy loading of sub-drivers commit 23f30268b9aa9944e68b20b56e036427b3b48395 Merge: 2c7d3f58 356c1d0f Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu Dec 11 13:57:48 2025 -0600 Merge pull request #2604 from SmartThingsCommunity/add/reset-energy-meter Matter Switch: Add ResetEnergyMeter command support commit 7235ce7b6c3b51710fddf61a4e830069cb5da2a5 Author: Alec Lorimer Date: Mon Nov 17 15:25:23 2025 -0600 CHAD-17081: zigbee-watering-kit lazy loading of sub-drivers commit 6f354e736615d5907b66227d1c6c7b6b045574bf Author: Steven Green Date: Wed Dec 10 15:21:45 2025 -0800 WWSTCERT-9423 second fingerprint commit c9b317e640654b4449f491931a9d615e5830be1a Author: Steven Green Date: Wed Dec 10 13:35:14 2025 -0800 fixup commit 2c7d3f582cc7fd4151e1176da36a86e80e9a417d Merge: 246a4c3c eb5724a8 Author: Alec Lorimer Date: Wed Dec 10 15:32:11 2025 -0600 Merge pull request #2582 from SmartThingsCommunity/zwave-thermostat-lazy-load-subdrivers CHAD-17040: Zwave thermostat lazy load subdrivers commit eb5724a8a4aa32030f85082c9769efaafe3e869b Author: Alec Lorimer Date: Mon Nov 17 15:25:21 2025 -0600 CHAD-17040: zwave-thermostat: Subdriver modifications for lazy loading commit ed4e8cd40039b9c505ed417204b057a2b01e6a69 Author: Alec Lorimer Date: Wed Dec 10 13:29:30 2025 -0600 CHAD-17043: Updates and fixes - Soft linking note fix - Command line arg for running script on a folder - Only checking for copyright in files in SmartThings driver folder commit 660eb54857406b938460ca40ed3dc1b1470e9019 Author: Alec Lorimer Date: Wed Dec 3 13:20:39 2025 -0600 CHAD-17043: Ensure executable commited and updated to soft linking logic commit 9ce9c5811316b3d78bfdf12f57a4d7fd5188bb7a Author: Alec Lorimer Date: Wed Dec 3 13:15:48 2025 -0600 CHAD-17043: Newline and SmartThings driver checking commit 799d8466c8bf3f4299163291be50a6fd69da28a2 Author: Alec Lorimer Date: Tue Dec 2 15:42:14 2025 -0600 CHAD-17043: Allow for range of years in copyright checking commit 9eca47dba0ca5a21d41f3047fdfba68afc4d0cb6 Author: Alec Lorimer Date: Tue Dec 2 14:43:15 2025 -0600 CHAD-17043: Copyright check no longer strict on year commit ad652120149c13a5a574eb15f07b042d2711c192 Author: Alec Lorimer Date: Tue Dec 2 13:10:52 2025 -0600 CHAD-17043: Removed testing line commit 82892a177347916b942b48d677e4961c3064e4d4 Author: Alec Lorimer Date: Tue Dec 2 13:03:01 2025 -0600 CHAD-17043: Created pre-commit script to check for newline and copyright on all staged lua files commit 246a4c3ce4aa7d8fc79bfdc08b4bfe56ab7b0aff Merge: 1c21ec55 d723c873 Author: Alec Lorimer Date: Wed Dec 10 11:16:59 2025 -0600 Merge pull request #2551 from SmartThingsCommunity/task/zigbee-motion-sensor-lazy-load-subdrivers CHAD-17042: zb-motion: Lazy loading of can handle commit 1c21ec551937fcdeca6c480bf889b2473a713046 Merge: 55711d70 684579f3 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue Dec 9 16:27:33 2025 -0600 Merge pull request #2615 from SmartThingsCommunity/add/aqs-modular-update-support Matter Sensor: Allow modular profile updates in infoChanged commit 684579f3677ff65d2f38a7794ed79e4ecc77ce7c Author: Harrison Carter Date: Tue Dec 9 14:48:30 2025 -0600 add update modular profile field commit 55711d70a5b03b84eac14de6ff19717110e5d66f Merge: ccb353b5 cedba53c Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue Dec 9 10:35:56 2025 -0600 Merge pull request #2607 from SmartThingsCommunity/deprecate/matter-lock-profiles Matter Lock: Deprecate old Matter Lock profiles commit a80097c016c1809558a4f549b53bcd82a38a717c Author: Steven Green Date: Mon Dec 8 17:03:10 2025 -0800 WWSTCERT-9343 WWSTCERT-9346 WWSTCERT-9349 WWSTCERT-9355 WWSTCERT-9358 commit ccb353b55eb7d0c1717bd02c8f663a2fed2e8365 Merge: 22fcff2c 4df2ed64 Author: Steven Green Date: Mon Dec 8 17:09:26 2025 -0800 Merge pull request #2566 from SmartThingsCommunity/new_device/WWSTCERT-9005 WWSTCERT-9005 Aqara Light Switch H2 US (2 Buttons, 2 Channel) commit b15a865061b88f614eeba1d923eb22113251f80d Author: Steven Green Date: Mon Dec 8 17:07:06 2025 -0800 WWSTCERT-9352 Ikea Bilresa commit 22fcff2c18a0d2fb78109bdfab890761dc2ebf17 Merge: 1350a65c 9bd3ef0e Author: Steven Green Date: Mon Dec 8 16:00:40 2025 -0800 Merge pull request #2605 from SmartThingsCommunity/new_device/WWSTCERT-9289 WWSTCERT-9289 Osram SMART MAT A53 DIM FILGD 824 E27 commit 1350a65c8598fd946c617e42f99d18ae3b603fb0 Merge: e1ad160e 4de04b6d Author: Steven Green Date: Mon Dec 8 15:47:21 2025 -0800 Merge pull request #2575 from SmartThingsCommunity/new_device/WWSTCERT-9051 WWSTCERT-9051 eufy FamiLock E32 commit e1ad160e0ceee42c74988303823c0ca20948968a Merge: 665f0eea 121b3c05 Author: Steven Green Date: Mon Dec 8 10:15:28 2025 -0800 Merge pull request #2608 from SmartThingsCommunity/fixup/frient add missing copyright notice commit 121b3c05f956cc77615ac127376b3943f0dff273 Author: Steven Green Date: Mon Dec 8 10:02:30 2025 -0800 add missing copyright notice commit cedba53c2e3bb4eee0f9c2aaf3ce6b2048d7bbc7 Author: Harrison Carter Date: Mon Dec 8 11:28:16 2025 -0600 deprecate old Matter Lock profiles commit 1224494b99a15132f177b51cd0ccbcd082da2499 Author: Alec Lorimer Date: Mon Nov 17 15:25:25 2025 -0600 CHAD-17071: zigbee-presence-sensor lazy loading of subdrivers commit 9bd3ef0e60b77929c8cc4bb0b79d5c3eba700ac1 Author: Steven Green Date: Fri Dec 5 11:41:12 2025 -0800 Osram SMART MAT A53 DIM FILGD 824 E27 commit 665f0eeacb288f30286238621a4a9dcf8945b958 Merge: ca08fb4c 9ec87af8 Author: Steven Green Date: Fri Dec 5 10:30:33 2025 -0800 Merge pull request #2594 from SmartThingsCommunity/new_device/WWSTCERT-9195 WWSTCERT-9195 WiZ Gradient Light Bars commit ca08fb4c96417b8fc1c5a8916316ee7e1aa5742f Merge: abe19284 ee41ab87 Author: Steven Green Date: Fri Dec 5 10:28:42 2025 -0800 Merge pull request #2593 from SmartThingsCommunity/new_device/WWSTCERT-9159 WWSTCERT-9159 Osram SMART MAT B40 TW 827 FR E14 commit abe19284c901d90db71304fb485f413985c16ef4 Merge: 53d126af 76f1f1a1 Author: Steven Green Date: Fri Dec 5 10:26:02 2025 -0800 Merge pull request #2592 from SmartThingsCommunity/new_device/WWSTCERT-9170 WWSTCERT-9170 Linkind Smart Light Bulb commit 53d126af13342984bdfc8b5570142b90b7ed0ae8 Merge: 63158a56 620b21cf Author: Steven Green Date: Fri Dec 5 10:15:41 2025 -0800 Merge pull request #2588 from caowei-cccc/Wistar WWSTCERT-9163 Add Wistar Smart Curtain Motor commit 356c1d0fd17d42bc1f09372305efffc821f8b61a Author: Harrison Carter Date: Fri Dec 5 11:58:51 2025 -0600 add ResetEnergyMeter command commit 63158a563e4c3a00add4c5d87e3db1bd5ee9d864 Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Thu Dec 4 11:02:01 2025 -0600 Matter Switch: Lazy load subdrivers if possible (#2525) Use the new `lazy_load_sub_driver_v2` api to lazy load subdrivers commit 9b2d2bf6f672b137081a54e1e82c0a1fc13ce368 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed Dec 3 15:30:00 2025 -0600 Matter Sensor: Improve error handling for unit conversion (#2602) commit 4f1be7e48ce659a97071bcc236994f71b5871e50 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed Dec 3 15:04:43 2025 -0600 Matter AQS: Break AQS files further apart, add supported air quality sensor value handling (#2587) commit 419a3ec4b6147a96ec893aa657979c73c7456ba0 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed Dec 3 14:40:20 2025 -0600 re-add switch vendor override check, add vendor override test (#2603) commit 14aa95e5d4e5c0fa940e258ebf470634fc33687b Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed Dec 3 12:47:52 2025 -0600 Matter Switch Subdriver: Add Bilresa Support (#2579) commit 9bb8e3984d283e6a0b779b3cff45bf0ceeed8fb4 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed Dec 3 11:40:45 2025 -0600 add handling for Power Topology cluster (#2444) commit d723c873887d6acba882e3d1a1cf3ccd3f98ec91 Author: Alec Lorimer Date: Thu Nov 13 01:34:44 2025 -0600 CHAD-17042:Lazy loading v2 of sub drivers and copyright updates commit 1f59baaf2316661d8b9313555ee3ad70bb9a2b2a Author: haedo-doo Date: Wed Dec 3 08:40:56 2025 +0900 WWSTCERT-8461 Aqara dimmer controller t1 (#2468) * add Aqara Dimmer Controller T1(0-10v) * Change the color temperature range without adding a profile * Fix test_aqara_led_bulb bug * fix deviceLabel format * add the new model instead of duplicating * fix preference bug * Copy the issue event to the added handler * Simplify the get_device_parameters code * add emit_event_if_latest_state_missing check code * remove unused function is_aqara_products in int.lua * Move the new model to can_handle.lua. commit 6a4aab5c8b5130970d8e5af260d174d3b0f4e4ed Merge: afbb8b3a ea8cbe05 Author: Alec Lorimer Date: Tue Dec 2 10:50:56 2025 -0600 Merge pull request #2577 from SmartThingsCommunity/zigbee-power-meter-lazy-load-subdrivers CHAD-17041: zigbee-power-meter: lazy loading sub-drivers v2 changes commit afbb8b3afeaad764d0aa5aaee4688272adbc8157 Author: Steven Green Date: Mon Dec 1 14:40:00 2025 -0800 fix timing issue with aqara test commit 9380f9c2149fb6ac351a7ff506ad2c3a65ae49e5 Author: Steven Green Date: Mon Dec 1 14:10:00 2025 -0800 Revert "Merge branch 'beta' into main" This reverts commit 48c52abf7835f140f03a8bdf0da56095b0186503, reversing changes made to d978bac0aadac7c9f88a54c0e1ba4580a29b312b. commit 48c52abf7835f140f03a8bdf0da56095b0186503 Merge: d978bac0 d5c5dc9a Author: Steven Green Date: Mon Dec 1 14:03:07 2025 -0800 Merge branch 'beta' into main commit d5c5dc9a2d89d97166807662a87f26489dc008f8 Merge: 16bd6225 e3ce206b Author: Steven Green Date: Mon Dec 1 12:53:42 2025 -0800 Merge pull request #2596 from SmartThingsCommunity/cherry-pick/12-1-25 Cherry-pick 12/1/25 commit e3ce206b159138a50d301344b61bc26d1303f966 Author: Tom Manley Date: Tue Nov 25 13:34:51 2025 -0600 Merge pull request #2580 from SmartThingsCommunity/feature/fp300 WWSTCERT-9021 Aqara Presence Multi-Sensor FP300 (update profile) commit e00f6714bff79da75706c9d1232baf05836349c2 Author: Steven Green Date: Wed Nov 26 10:44:18 2025 -0800 WWSTCERT-9105 Meross Smart Presence Sensor (Thread) (#2578) * WWSTCERT-9105 Meross Smart Presence Sensor (Thread) * use non-deprecated profile * use correct profile name commit ff7d391865fb288e01e629254e6ac447d9574eeb Author: Steven Green Date: Wed Nov 26 10:34:13 2025 -0800 Merge pull request #2576 from SmartThingsCommunity/new_device/WWSTCERT-9074 WWSTCERT-9074 Cync Fan Switch commit 16fbe0013cef4b6c69a3ea8a15a5293e2a6fdae7 Author: Steven Green Date: Mon Nov 24 11:29:30 2025 -0800 Merge pull request #2567 from SmartThingsCommunity/new_device/WWSTCERT-9021 WWSTCERT-9021 Aqara Presence Multi-Sensor FP300 commit d978bac0aadac7c9f88a54c0e1ba4580a29b312b Merge: 961bc1b8 ca527452 Author: Steven Green Date: Mon Dec 1 11:53:21 2025 -0800 Merge pull request #2595 from SmartThingsCommunity/new_device/WWSTCERT-9060 WWSTCERT-9060 Resideo Valve Controller commit ca527452360c9a61d88f3a0fbc2ae6c0c96106fb Author: Steven Green Date: Mon Dec 1 11:48:43 2025 -0800 WWSTCERT-9060 Resideo Valve Controller commit 9ec87af8e0b913a3878a070e49e1bfd55cab00f3 Author: Steven Green Date: Mon Dec 1 11:44:48 2025 -0800 WWSTCERT-9195 WiZ Gradient Light Bars WWSTCERT-9198 WiZ Gradient Light Bars WWSTCERT-9209 WiZ Gradient Floor lamp WWSTCERT-9212 WiZ Gradient Floor lamp commit ee41ab878c755261590c27c58b446346cf32291b Author: Steven Green Date: Mon Dec 1 11:37:26 2025 -0800 WWSTCERT-9159 Osram SMART MAT B40 TW 827 FR E14 commit 76f1f1a1fdb6df0a9db766e8e7aeec2bfab664f5 Author: Steven Green Date: Mon Dec 1 11:34:12 2025 -0800 WWSTCERT-9170 Linkind Smart Light Bulb commit 961bc1b86808b5e307739d940c2973b80c4770b1 Merge: ef928eb5 5aeb76d9 Author: Steven Green Date: Mon Dec 1 10:50:05 2025 -0800 Merge pull request #2586 from SmartThingsCommunity/new_device/WWSTCERT-9113 WWSTCERT-9113 LUX TQX Smart Thermostat commit ef928eb57f460130b63d727a7b43e0273a460851 Merge: 974ea619 fb88d263 Author: Steven Green Date: Mon Dec 1 10:46:31 2025 -0800 Merge pull request #2585 from SmartThingsCommunity/new_device/WWSTCERT-9117 WWSTCERT-9117 OSRAM MATTER PLUG UK commit 974ea619c5e34164b17c903773c6a152d2d3ab83 Merge: 37691d50 641a5a7c Author: Steven Green Date: Mon Dec 1 10:15:35 2025 -0800 Merge pull request #2589 from FrankSpringfield/main [PLM251120-09263][PLM251120-09698] Hide some specific events in history commit 641a5a7cabc1cff02db63524c407d6da04723386 Author: FrankSpringfield Date: Fri Nov 28 16:09:03 2025 +0800 [PLM251120-09263][PLM251120-09698] Hide some specific events in history Signed-off-by: FrankSpringfield commit 620b21cfc711c00e5f66cb5b6f936533788939b6 Author: cao wei Date: Thu Nov 27 10:38:09 2025 +0800 Add Wistar Smart Curtain Motor Signed-off-by: cao wei commit 37691d50b13b618f7ce058743a5ce9c9972d873d Author: Inovelli <37669481+InovelliUSA@users.noreply.github.com> Date: Wed Nov 26 13:44:19 2025 -0700 WWSTCERT-8360 Inovelli VZW32-SN: Adding support for this device for the WWST program (#2371) * Add support for Inovelli mmWave switch * Fix for measurement unit being cm (not mm) * Configure illuminance reporting and fix p101-106 unit incorrect * adjusting lux reporting. remove p117 as mmwave param and updated its options * initializing values for occupancy, illuminance, and binding to occupancy cluster * add ability to reset energy meter * add ability to reset energy meter * adjusting some default parameters and adding ota image notify for firmware update process during certification * adding missing OTAUpgrade declaration * Inovelli - adding support for vzw32-sn for wwst * accidentally included some vzm32-sn (zigbee) changes in this branch. Removing them * some updates from linter results. Notification turns on when color changed. Update the app UI * Removing unsupported capabilities * fixing mmwave reset command * remove some preferences. Prevent extra events being sent for child device during init * add copyright info * remove firmware update capability * remove unused configuration handler and notification class * removing on/off and level handlers * removing version report features * add unit tests * updating notification profile as requested by ST * update unit test for different rgbw profile * remove versionget command * fix lua errors * some more fixes for test unit files * missed an unused variable * modify unit test files. Changes to device init to be more efficient * Remove unused code * linter fixes * remove unused init function * fixing linter error * removing firmware update from rgbw bulb. Adding custom refresh handler * modify refresh command so it doesn't kill the driver * consolidating drivers. Updating some unit tests * Fixing some linter warnings * fix inovelli button test error * fix linter warning on button test * fix color init for lzw31 black switch * better naming of driver files * will updated supportedValues for held and down_hold events. This can likely be removed in the future * linter warnings fixed * linter warnings fixed * fix minor formatting issues and copyright issue --------- Co-authored-by: InovelliUSA commit c0f12f8d20b188f5ef65820e7bdab1805745a8c6 Author: Steven Green Date: Wed Nov 26 10:44:18 2025 -0800 WWSTCERT-9105 Meross Smart Presence Sensor (Thread) (#2578) * WWSTCERT-9105 Meross Smart Presence Sensor (Thread) * use non-deprecated profile * use correct profile name commit b69e1d518e0493d7ba7704c80538f3f82f5535f3 Author: Inovelli <37669481+InovelliUSA@users.noreply.github.com> Date: Wed Nov 26 11:41:18 2025 -0700 WWSTCERT-8802 Inovelli: adding support for vzm30 (zigbee on/off) and adding supported button events to all de… (#2537) * adding support for vzm30 and adding supported button events to all devices * removing some preferences. update test unit * fix linter errors for test files * better naming for drivers * updating supportedValues for devices that may have been included with the old driver * fixing linter errors * fixing linter errors * fixing linter errors commit 32fe7762867008193d11a5df776e5c4fbe549d60 Merge: dc132c99 d7460ea0 Author: Steven Green Date: Wed Nov 26 10:34:13 2025 -0800 Merge pull request #2576 from SmartThingsCommunity/new_device/WWSTCERT-9074 WWSTCERT-9074 Cync Fan Switch commit 5aeb76d928fa22e14d56a9523e5f40be47a5dca1 Author: Steven Green Date: Tue Nov 25 14:40:22 2025 -0800 WWSTCERT-9113 LUX TQX Smart Thermostat commit fb88d26353165d112e7f40df0030497c8ed618db Author: Steven Green Date: Tue Nov 25 14:37:37 2025 -0800 WWSTCERT-9117 OSRAM MATTER PLUG UK WWSTCERT-9120 WWSTCERT-9123 WWSTCERT-9126 WWSTCERT-9129 WWSTCERT-9132 WWSTCERT-9135 commit dc132c99dc8485b0b7b54b6419fa12eef2587c7e Merge: 5b9ff3af 73556667 Author: Tom Manley Date: Tue Nov 25 13:34:51 2025 -0600 Merge pull request #2580 from SmartThingsCommunity/feature/fp300 WWSTCERT-9021 Aqara Presence Multi-Sensor FP300 (update profile) commit 5b9ff3af1be94067fc9d547fdded2b032abe7fe1 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue Nov 25 13:22:48 2025 -0600 deprecate matter- prefixed profiles (#2583) commit 7355666729403bf40cbe051b3f9c5086b01af8f2 Author: Tom Manley Date: Tue Nov 25 10:55:07 2025 -0600 WWSTCERT-9021 Aqara Presence Multi-Sensor FP300 (update profile) commit 2e7ffb96480bfa7210f05d9024f2898f9d707d65 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon Nov 24 16:35:36 2025 -0600 reorder the matter thermostat directory structure (#2548) commit d7460ea07ab2acdb0ce19968f8a0745e981955fc Author: Steven Green Date: Mon Nov 24 11:44:07 2025 -0800 WWSTCERT-9074 Cync Fan Switch commit 4de04b6d51fe67deef04a2ea23126a9eb3249aef Author: Steven Green Date: Mon Nov 24 11:41:11 2025 -0800 WWSTCERT-9051 eufy FamiLock E32 commit 5ca5f9c40ac33d2bc11e09773505509570983e47 Author: Steven Green Date: Mon Nov 24 11:33:36 2025 -0800 WWSTCERT-8445 Kwikset Halo Select Plus (#2498) * WWSTCERT-8445 Kwikset Halo Select Plus * add to new matter lock commit a4e190468fd2d946b2118cc1a82779a04b026ed7 Merge: d957e692 713c658d Author: Steven Green Date: Mon Nov 24 11:29:30 2025 -0800 Merge pull request #2567 from SmartThingsCommunity/new_device/WWSTCERT-9021 WWSTCERT-9021 Aqara Presence Multi-Sensor FP300 commit d957e692f1ee0936eabc2d34bd00305ec3830354 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon Nov 24 13:28:36 2025 -0600 Matter Switch: Simplify component to endpoint mapping logic (#2560) commit 2389712bd840942d1d641a89b3ef4e0b48efefe6 Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Mon Nov 24 10:22:16 2025 -0600 Matter Camera: Check for server cluster type (#2570) Check for cluster_type = SERVER or BOTH when adding optional capabilities. When using get_endpoints, omit the check for SERVER because it is already implied in that api. commit 16bd6225afabba7fa33e4ed756ec80063bae6887 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu Nov 20 11:14:31 2025 -0600 Fix webrtc capability inclusion requirements (#2568) (#2571) Co-authored-by: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> commit 1395f676f41755ef2ba1ec829fd9778f31f6f2ba Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Wed Nov 19 17:03:50 2025 -0600 Fix webrtc capability inclusion requirements (#2568) In the matter camera subdriver, the webrtc capability should be included if the `WebRTCTransportProvider` clusters is implemented as `SERVER` and if `WebRTCTransportRequestor` is implemented as `CLIENT`; this commit adds a check for the former which is currently missing from the subdriver. commit b828a5a984312da95f456241b00a5c1ffef90178 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed Nov 19 15:29:56 2025 -0600 Matter Switch: Add more reliability to device type checking (#2564) commit e628b473e4b26e3c7deeee4b3132928a2ce2008e Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Wed Nov 19 15:08:19 2025 -0600 Matter Window Covering: Remove default tilt preset (#2565) Remove the default tilt preset level of 50%. commit ff41419c6cee98c72f62f680331fdcb61ce5bea1 Merge: 2994e5ea 165feec0 Author: NoahCornell <67164901+NoahCornell@users.noreply.github.com> Date: Wed Nov 19 10:47:51 2025 -0600 Merge pull request #2556 from SmartThingsCommunity/sonos_ssdp_spam sonos: Retry ssdp task creation to fix log spam and spinning commit 713c658d70e61bb96b1fecbe0f8c65b884c3db28 Author: Steven Green Date: Tue Nov 18 16:10:37 2025 -0800 WWSTCERT-9021 Aqara Presence Multi-Sensor FP300 commit 4df2ed64c0a97cfc84cd5fb9ae2cf58359d948e3 Author: Steven Green Date: Tue Nov 18 16:06:26 2025 -0800 WWSTCERT-9005 Aqara Light Switch H2 US (2 Buttons, 2 Channel) WWSTCERT-9008 Aqara Light Switch H2 US (4 Buttons, 3 Channel) WWSTCERT-9011 Aqara Light Switch H2 US (2 Buttons, 1 Channel) WWSTCERT-9014 Aqara Light Switch H2 EU (2 Buttons, 1 Channel) WWSTCERT-9017 Aqara Light Switch H2 EU (4 Buttons, 2 Channel) commit 2994e5eaa2b2217e7b00125cf0fd9e58b01b3dd2 Author: Marcin Krystian Tyminski <81477027+marcintyminski@users.noreply.github.com> Date: Tue Nov 18 20:30:25 2025 +0100 WWSTCERT-8756 Add support to frient air quality sensor (#2439) * added support to frient air quality sensor * delete device:add_monitored_attribute from the driver * remove unused variable * change profile * removed unused health concern value * removed log statements commit c178696137b62cb53b75c6ee52b2e1cc7becdfdb Author: seojune79 <119141897+seojune79@users.noreply.github.com> Date: Wed Nov 19 04:15:09 2025 +0900 WWSTCERT-8722/8725 [Aqara] add Aqara Wireless Remote Switch H1(Single/Double) (#2511) * add Aqara Wireless Remote Switch H1(Single/Double) * remove the commented‑out code commit 5d997ba829de6fc70aefd70787435d1b7586a700 Merge: 9fa64344 d0190561 Author: Steven Green Date: Mon Nov 17 14:42:18 2025 -0800 Merge pull request #2490 from zhouchengyu-cell/WISTAR-Smart-Vertical-Blind-Motor WWSTCERT-8475/8479/8483 Added three WISTAR Smart Vertical Blind Motors commit ea8cbe05db0f03099fb29b6a4d9fb1285b76ee0b Author: Alec Lorimer Date: Mon Nov 17 15:25:03 2025 -0600 zigbee-power-meter:[automated] lazy loading changes commit 3da67502ebf8cf3ce552ee7855bec1e2a88b14a5 Merge: 04849b4c 9fa64344 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon Nov 17 14:28:40 2025 -0600 Merge pull request #2562 from SmartThingsCommunity/main Main->Beta 11/17/2025 commit 04849b4cf8db7df12280e89548a8ab4ced687282 Author: Steven Green Date: Fri Nov 14 10:38:00 2025 -0800 Merge pull request #2553 from seojune79/Aqara-WirelessRemote-HeldEvt [Aqara] Fixed an issue where an unintended event was triggered (or generated) when the button was long-pressed commit 03e2fc15d73cfddb846bf8292e69f416ff70e40c Author: Steven Green Date: Tue Nov 11 12:32:09 2025 -0800 Merge pull request #2543 from SmartThingsCommunity/new_device/WWSTCERT-8812 WWSTCERT-8812 Heiman Smart Humidity&Temperature Sensor commit 9fa64344f94356933cd08cfa46e24965eba65e2d Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Mon Nov 17 10:56:46 2025 -0600 Lazy load camera subdriver only for LuaLibs 16 and greater (#2558) The camera subdriver is only supported on LuaLibs version >= 16, and causes issues if it's loaded on previous versions, so this commit changes `lazy_load_if_possible` to `lazy_load`, which only attempts to pull in the subdriver if the api version if 16 or greater. commit 4bce0f392755b1f068739a78b87ec682c4bd0002 Merge: a4e2a024 3fc413e3 Author: Douglas Stephen Date: Mon Nov 17 08:55:10 2025 -0600 Merge pull request #2533 from SmartThingsCommunity/fix/hue-multi-button-inconsistencies commit a4e2a024608bfa5e71f4acdd5e628992705f0575 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Fri Nov 14 14:38:11 2025 -0600 Matter Switch: Write EXECUTE_IF_OFF bit to Control Cluster Options attribute (#2535) commit eb90b37a50739f58c4b12188a46265ad458278e9 Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Fri Nov 14 14:21:34 2025 -0600 Add driver support for Matter Cameras (#2503) commit 165feec0a09eeded950a74c53a0c5e8a04a694ae Author: NoahCornell Date: Fri Nov 14 13:27:15 2025 -0600 sonos: Retry ssdp task creation to fix log spam and spinning commit 9fd228deb2c2798e68ef1462c5a3347d3c93dbeb Merge: 16cef63e 99161677 Author: Steven Green Date: Fri Nov 14 10:38:00 2025 -0800 Merge pull request #2553 from seojune79/Aqara-WirelessRemote-HeldEvt [Aqara] Fixed an issue where an unintended event was triggered (or generated) when the button was long-pressed commit 99161677d180d21c2d5c4d3da801f02cff44af6d Author: seojune Date: Fri Nov 14 16:06:44 2025 +0900 A problem occurred where two events were triggered (on press and on release) when the button was long-pressed, so the implementation was modified to only trigger an event upon a long press. commit 16cef63ed0b923c00c678eb478157b0b9b8e3b79 Author: Marcin Krystian Tyminski <81477027+marcintyminski@users.noreply.github.com> Date: Fri Nov 14 00:39:32 2025 +0100 WWSTCERT-8242 Add support to frient heat detector (#2447) * Add support to frient heat detector * removed whitspaces * deleted whitespace * Change the tests to increase code coverage * change the date in test file * Change tests to increase code coverage commit a53030218cbf27a1b1a68def21e81cba32428d49 Author: seojune79 <119141897+seojune79@users.noreply.github.com> Date: Thu Nov 13 07:27:00 2025 +0900 [Aqara/Wireless Remote Switch] Added a battery capability update for users using the previous driver… (#2550) * Added a battery capability update for users using the previous driver version. * Refine battery percentage calculation function commit f1557c47830f2ddb0071c2f9746d729e0719edf7 Author: Trfwww Date: Thu Nov 13 04:35:20 2025 +0800 Add devices Onvis Smart Plug S4EU (#2516) Signed-off-by: Tian Rf commit d572842e3cebf9b2759e8caaa906d9cb2626dc6e Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue Nov 11 16:38:17 2025 -0600 default Eve devices to main driver (#2534) commit 658e57be19bae5122d151cccfd6325d91c348692 Merge: d6dbbcf6 622aefa9 Author: Alec Lorimer Date: Tue Nov 11 15:33:59 2025 -0600 Merge pull request #2518 from SmartThingsCommunity/chore/zwave-switch-ll-refactor zwave-switch lazy loading of sub drivers refactor commit 622aefa911eea6b8e4857f757d87578f75f660ef Merge: af80a9d8 d6dbbcf6 Author: Alec Lorimer Date: Tue Nov 11 15:04:31 2025 -0600 Merge branch 'main' into chore/zwave-switch-ll-refactor commit d6dbbcf67f8efb09518097c40be9926f1a38b230 Merge: a862ebb7 588bd128 Author: Steven Green Date: Tue Nov 11 12:35:55 2025 -0800 Merge pull request #2424 from thinkaName/yanmi-Y-K00x-001 WWSTCERT-8785 add yanmi Y-K003-001 switch commit a862ebb7f82057d3ce2ab02ea2a7967ef5e67fd1 Merge: 737773e6 a0bb0199 Author: Steven Green Date: Tue Nov 11 12:32:09 2025 -0800 Merge pull request #2543 from SmartThingsCommunity/new_device/WWSTCERT-8812 WWSTCERT-8812 Heiman Smart Humidity&Temperature Sensor commit c494e3658108c277e0c55578a107296061619da1 Merge: 795021f1 737773e6 Author: Steven Green Date: Mon Nov 10 11:57:13 2025 -0800 Merge pull request #2547 from SmartThingsCommunity/main Rolling up main to beta commit 795021f1f0330300d741cb9ab654dcc9e1ebe0b4 Merge: c52da95c e5f25aa4 Author: Steven Green Date: Mon Nov 10 11:37:16 2025 -0800 Merge pull request #2545 from SmartThingsCommunity/cherry-pick/11-10-25 Cherry pick/11 10 25 commit e5f25aa472201d671cde7571a855323580e67b61 Author: Carter Swedal Date: Thu Nov 6 13:27:17 2025 -0600 Merge pull request #2538 from SmartThingsCommunity/lua_libs_v16_test_fixes Stop accounting for leakage of variables between tests commit af80a9d879d647e4543229129fb5cd5277fe684e Author: Alec Lorimer Date: Mon Nov 10 13:15:58 2025 -0600 Formatting fixes - Newlines at the end of every lua file - moved fingerprints with less than 5 into the can_handle file - Removed a missed old copyright notice - Tested on lua libs release 0.59 (commit ID: 546728a3d24ee825f7a9db89c349abbb22949926) commit 0c93124ac6c33d7329fc29bdc88cc3ca8e399f7a Author: Steven Green Date: Fri Nov 7 11:01:04 2025 -0800 Merge pull request #2541 from SmartThingsCommunity/new_device/WWSTCERT-8788 WWSTCERT-8788 NodOn Zigbee Multifunction Relay Switch with Metering commit 8403a399a3fc14d342a71e12060609064758d359 Author: Steven Green Date: Thu Nov 6 15:50:37 2025 -0800 Merge pull request #2531 from SmartThingsCommunity/new_device/WWSTCERT-8759 WWSTCERT-8759 OSRAM MATTER CLASSIC A 100W commit c5cae5b6fb7ced332ca0058e8acd392ba4afffdc Author: Steven Green Date: Thu Nov 6 15:43:27 2025 -0800 Merge pull request #2527 from SmartThingsCommunity/new_device/WWSTCERT-8677 WWSTCERT-8677 Leviton Decora Smart Wi-Fi (2nd Gen) Scene Controller S… commit ab076971cd906330ed5078a0e1473e2ade666973 Author: Alec Lorimer Date: Tue Nov 4 15:39:25 2025 -0600 Update copyright to 2025 and shorter notice commit ce46751bacf23ea6b89ba78221de389b7f677e85 Author: Alec Lorimer Date: Mon Nov 3 15:15:16 2025 -0600 chore/zwave-switch lazy loading of sub drivers refactor commit 380effce6d1766b6c9167b202fd9d10bd80db426 Author: Steven Green Date: Tue Nov 4 14:41:44 2025 -0800 Merge pull request #2529 from SmartThingsCommunity/new_device/WWSTCERT-8703 WWSTCERT-8703 NodOn Zigbee ON/OFF Lighting Relay Switch commit 737773e665a0dfb097501e8d0b23234b88360c1d Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon Nov 10 11:15:24 2025 -0600 Matter Sensor: Re-organize the directory structure (#2521) commit 588bd128a1b0cdd51caf04fad637e523cbfc658d Author: thinkaName <962679819@qq.com> Date: Mon Nov 10 10:38:16 2025 +0800 add yanmi Y-K003-001 switch commit a0bb019957752dfb4043f5445bd043f6343b5717 Author: Steven Green Date: Fri Nov 7 11:04:18 2025 -0800 WWSTCERT-8799 Heiman Smart Humidity&Temperature Sensor commit 09751957ede65da96c526fb49fd3070a01e85b2d Merge: fe527a4a e905a119 Author: Steven Green Date: Fri Nov 7 11:01:04 2025 -0800 Merge pull request #2541 from SmartThingsCommunity/new_device/WWSTCERT-8788 WWSTCERT-8788 NodOn Zigbee Multifunction Relay Switch with Metering commit e905a119c72094e8aa59b94ea1308bc0b3588d45 Author: Steven Green Date: Fri Nov 7 10:53:21 2025 -0800 WWSTCERT-8788 NodOn Zigbee Multifunction Relay Switch with Metering commit fe527a4aa72d0a306b7a3723d61e630f9f36d8eb Merge: 1c389f62 fbfbc3ad Author: Steven Green Date: Thu Nov 6 15:53:41 2025 -0800 Merge pull request #2465 from thinkaName/HOPOsmart_window_puser WWSTCERT-8408 add HOPOsmart window opener A2230011 commit 1c389f62378e28b82139780b3aba7c8e691ffcdd Merge: 8de39701 1dabb813 Author: Steven Green Date: Thu Nov 6 15:50:37 2025 -0800 Merge pull request #2531 from SmartThingsCommunity/new_device/WWSTCERT-8759 WWSTCERT-8759 OSRAM MATTER CLASSIC A 100W commit 8de397010b36d8b1db20c9070d0085e30c7f621a Merge: fd370c2b 445f9777 Author: Steven Green Date: Thu Nov 6 15:43:27 2025 -0800 Merge pull request #2527 from SmartThingsCommunity/new_device/WWSTCERT-8677 WWSTCERT-8677 Leviton Decora Smart Wi-Fi (2nd Gen) Scene Controller S… commit fd370c2bcc76d6a30fa3e2689c25bb96f34b30ea Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Thu Nov 6 13:57:00 2025 -0600 Fix test cases using EnergyMeasurementStruct (#2470) Two fields were added to the type definition for EnergyMeasurementStruct and must be added to tests using this struct to fix test case failures. commit 570354bf49b1bc8495d57ac859b92cc2f1ef07fd Merge: cc879b58 09f6fc73 Author: Carter Swedal Date: Thu Nov 6 13:27:17 2025 -0600 Merge pull request #2538 from SmartThingsCommunity/lua_libs_v16_test_fixes Stop accounting for leakage of variables between tests commit 09f6fc73f90e8fba2b4a5f2f8484e96877699fc4 Merge: e8d0bd38 cc879b58 Author: Carter Swedal Date: Thu Nov 6 13:25:00 2025 -0600 Merge branch 'main' into lua_libs_v16_test_fixes commit cc879b5851282fb6b5bcab439107b4480f6a50c7 Merge: d54d106b d42421a1 Author: Carter Swedal Date: Thu Nov 6 13:05:21 2025 -0600 Merge pull request #2469 from SmartThingsCommunity/test/zb-switch-memory-reduction Lazy loading test for zigbee switch commit e8d0bd384e33aaa1434f61d01a650e12c03e48cf Author: Gene Harvey Date: Thu Nov 6 11:03:16 2025 -0600 Stop accounting for leakage of variables between tests Due to changes in the v16 API, certain variable values are no longer leaked between tests. This commit accounts for that. commit d54d106b2e1b718a2d1f108aeea2c8f18aedfd17 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Thu Nov 6 09:36:57 2025 -0600 remove smokeSensitivity preference from profile (#2532) commit 3fc413e3b4cdccc440a2dcd8e2aa2fb3115ee22c Author: Doug Stephen Date: Wed Nov 5 12:33:55 2025 -0600 fix: Multi-button service mapping improvements. For multi-button devices, we were waiting to populate the mappings to associate the multiple button service resources to the single device record from the API responses we perform during the `init` handler. This could lead to inconsistent state or failure to maintain mappings in situations where those API calls failed for any reason. We still need to make those API calls during `init` to establish the current state of the device, but we now populate the mappings from cached data (if it exists) instead of relying on those initial API calls to resolve properly. commit a512c65cf38fc23422b08363ab3ad82634f6ef91 Author: Doug Stephen Date: Wed Nov 5 12:28:30 2025 -0600 fix: Relax timeouts for threaded REST responses. commit 4a790ec0dbf297bd9c5703a6db95c72e351590e6 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed Nov 5 11:21:41 2025 -0600 Matter Switch: Update Directory Names and Copyright Information (#2524) * update Matter Switch file organization * add copyright to embedded clusters commit fbfbc3adc180f14368f382244d1078cb74149343 Author: thinkaName <962679819@qq.com> Date: Wed Nov 5 13:51:46 2025 +0800 add HOPOsmart window opener A2230011 commit 1dabb8137fd3c4eeb793a7edb8703441afa44511 Author: Steven Green Date: Tue Nov 4 14:49:32 2025 -0800 WWSTCERT-8759 OSRAM MATTER CLASSIC A 100W WWSTCERT-8762 SMART MAT A60 RGBW 827 FR E27 commit dd6383875b3242c0a67ecfc34911b6b4404ec173 Merge: 778b5a9e b2d9b1c8 Author: Steven Green Date: Tue Nov 4 14:41:44 2025 -0800 Merge pull request #2529 from SmartThingsCommunity/new_device/WWSTCERT-8703 WWSTCERT-8703 NodOn Zigbee ON/OFF Lighting Relay Switch commit b2d9b1c847f6334a65989bc99bdf8cfbcdf32477 Author: Steven Green Date: Tue Nov 4 14:26:30 2025 -0800 WWSTCERT-8703 NodOn Zigbee ON/OFF Lighting Relay Switch commit 445f9777fb45780a870d80a6071fd227f95e82e3 Author: Steven Green Date: Tue Nov 4 14:20:03 2025 -0800 WWSTCERT-8677 Leviton Decora Smart Wi-Fi (2nd Gen) Scene Controller Switch commit 778b5a9e5f6309e8eb1dd7cb6c0b44cf5b595df6 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue Nov 4 11:50:43 2025 -0600 Matter Smoke Alarm: add smoke-temp-humidity-battery profile (#2513) commit c52da95c5cc938b8e43a3b2c8117bb74ddfe85b9 Merge: 050098b4 07243057 Author: Steven Green Date: Mon Nov 3 14:07:42 2025 -0800 Merge pull request #2522 from SmartThingsCommunity/main Rolling up main to beta commit d42421a180b52eae1c557bed10ddf53a26704ec8 Author: cjswedes Date: Mon Nov 3 15:25:41 2025 -0600 Update copywrites to be shorter and add them where missing This is done to maintain consitency across the whole driver and set an example for other drivers to follow. The reduced source file comment should also help with memory savings since comments do affect memory of a driver when the driver code is being loaded into the Lua runtime. commit 056ee617f8808810a0ba3d4f9fbd2965b621791f Author: cjswedes Date: Fri Oct 10 12:22:20 2025 -0500 Enable use of lazy_load_subdriver_v2 in zigbee switch This is a new feature in api v16 that allows for more memory savings by more efficiently lazily loading the subdriver. Subdrivers must have their can_handle function and their own subdrivers split out into separate modules to work with the new lazy loading api. commit 050098b460f485da79a400992eff7ae571dba5f8 Merge: 3e8b9d51 5f6faa78 Author: Steven Green Date: Mon Nov 3 13:41:18 2025 -0800 Merge pull request #2519 from SmartThingsCommunity/cherry-pick/11-3-25 Cherry pick/11 3 25 commit 5f6faa7838faa2d8f9fef662982b0924a7d2829d Author: Steven Green Date: Wed Oct 29 11:29:25 2025 -0700 Merge pull request #2509 from SmartThingsCommunity/new_device/WWSTCERT-8659 WWSTCERT-8637 NodOn Zigbee Roller Shutter Relay Switch commit 46c5e2d9c58ab0663d87ee980f7f24426bddd8ce Author: Steven Green Date: Wed Oct 29 11:07:12 2025 -0700 Merge pull request #2508 from SmartThingsCommunity/new_device/WWSTCERT-8637 WWSTCERT-8637 NodOn Zigbee Multifunction Relay Switch commit 0724305781777cb640a20700b03624c1242bcf91 Author: Inovelli <37669481+InovelliUSA@users.noreply.github.com> Date: Mon Nov 3 12:12:56 2025 -0700 WWSTCERT-8354 Add support for Inovelli mmWave switch VZM32-SN (#2228) * Add support for Inovelli mmWave switch * Fix for measurement unit being cm (not mm) * Configure illuminance reporting and fix p101-106 unit incorrect * adjusting lux reporting. remove p117 as mmwave param and updated its options * initializing values for occupancy, illuminance, and binding to occupancy cluster * add ability to reset energy meter * add ability to reset energy meter * adjusting some default parameters and adding ota image notify for firmware update process during certification * adding missing OTAUpgrade declaration * removing unused capability * turning on notification child device when color set * fix mmwave reset command * adding ota image select preference * making some modificastions requested by ST * using default illuminance handler * removing some preferences and changing illuminance calculation method * removing extra line and white space * Making some code more efficient and adding unit test files. * Fix linter errors * Fixing more linter errors * adding more unit tests for energy, power, and illuminance reporting * adding unit tests for occupancy. Fix some linter errors * small linter errors fix * adding more unit tests * Fix minor linter error * combining inovelli vzm31 and vzm32 drivers. Add test unit for vzm31 * removing unused files * fix linter errors * remove test unit file * add more unit tests --------- Co-authored-by: InovelliUSA commit 08ea8eb2faf6e6bf17a92c1d94af276cbd8d89d6 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Fri Oct 31 12:15:47 2025 -0500 Add greater dynamic profiling to matter switch (#2481) commit b53f7bbb468690b0d173b1ad7f2eb2fe5dfd1c14 Author: seojune79 <119141897+seojune79@users.noreply.github.com> Date: Fri Oct 31 04:20:30 2025 +0900 WWSTCERT-8471/8468 [Aqara] Wireless Remote Switch T1(Single/Double) (#2458) * add Aqara Wireless Remote Switch T1(Single/Double) * change the categories of the main component from 'Button' to 'RemoteController' in the 'aqara-double-buttons' profile * Added the firmwareUpdate capability to aqara-single-button.yml and renamed the file to one-button-batteryLevel.yml. * Changing the device profile of Aqara Button Devices * dd the firmwareUpdate capability to the aqara-double-buttons device profile. commit 73c36a8cb65d0575a7784621d1eeba5d8395c8d0 Merge: 8c760324 8b4b5c88 Author: Steven Green Date: Wed Oct 29 11:29:25 2025 -0700 Merge pull request #2509 from SmartThingsCommunity/new_device/WWSTCERT-8659 WWSTCERT-8637 NodOn Zigbee Roller Shutter Relay Switch commit 8b4b5c88ea4012b6204e559b9a696fb42aadc6c8 Author: Steven Green Date: Wed Oct 29 11:10:01 2025 -0700 WWSTCERT-8637 NodOn Zigbee Roller Shutter Relay Switch commit 8c76032456ab38cc2bae5f868bb75fb169c467b7 Merge: 9331e5d7 604a7be3 Author: Steven Green Date: Wed Oct 29 11:07:12 2025 -0700 Merge pull request #2508 from SmartThingsCommunity/new_device/WWSTCERT-8637 WWSTCERT-8637 NodOn Zigbee Multifunction Relay Switch commit 604a7be3e5dd5f3d42681e048f2cec07605a8637 Author: Steven Green Date: Wed Oct 29 10:59:56 2025 -0700 WWSTCERT-8637 NodOn Zigbee Multifunction Relay Switch commit 9331e5d7ee9c93c302e0d4fa32b171f5f6c8a86e Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Tue Oct 28 15:15:30 2025 -0500 Matter Switch: Update Vendor Overrides (#2480) * update vendor overrides commit af874198fd9239b1c0da99204791a9b67991e8cb Author: Cornelius117 <72489457+Cornelius117@users.noreply.github.com> Date: Wed Oct 29 00:04:35 2025 +0800 Add processing logic of motion sensor in button devices (#2459) * Add processing logic of motion sensor in button devices * delete white space * Delete duplicated main component * Add restrictions commit 3e8b9d516b7721d9fd0c1974b3adc576b03be225 Merge: ac9e00a9 ea366169 Author: Steven Green Date: Mon Oct 27 13:45:31 2025 -0700 Merge pull request #2502 from SmartThingsCommunity/main Rolling up main to beta commit ac9e00a9a6e42bd75a3acd2f192bdaa992cec359 Merge: 516472a6 6ec3412b Author: Steven Green Date: Mon Oct 27 12:18:59 2025 -0700 Merge pull request #2500 from SmartThingsCommunity/cherry-pick/10-27-25 Cherry-pick 10/27/25 commit 6ec3412ba3e92ec6a7cfc7256b37f004b3d1ecca Author: Steven Green Date: Thu Oct 23 11:39:43 2025 -0700 WWSTCERT-8415 Decora Smart Motion Sensing Dimmer Switch (#2489) commit 83e3b75f1f93a5317a52b21116976d06df5c2e6d Author: Steven Green Date: Thu Oct 23 11:38:41 2025 -0700 WWSTCERT-8340 Meross Smart Wi-Fi Roller Shutter Timer (#2488) commit 99ac8b2fe45aec4ea74fbf712b513d86c025774e Author: Steven Green Date: Wed Oct 22 14:21:28 2025 -0700 WWSTCERT-8297 Meross Smart Wi-Fi Switch (#2473) * WWSTCERT-8297 Meross Smart Wi-Fi Switch WWSTCERT-8300 Meross Smart Wi-Fi Switch WWSTCERT-8303 Meross Smart Wi-Fi Power Strip * update profile commit ea366169454dcb50653ba7e8d0d02155eb403bda Merge: cc30d984 8e3bc94e Author: Douglas Stephen Date: Mon Oct 27 09:45:02 2025 -0500 Merge pull request #2493 from SmartThingsCommunity/fix/sonos-nil-iteration-crash commit 8e3bc94ea4549405fb3639c129328664ab016f66 Author: Doug Stephen Date: Thu Oct 23 10:13:55 2025 -0500 fix: guard against potential nil table method call commit e7f9521865cb9329b5f2a8570e94d28a8920bc37 Author: Doug Stephen Date: Thu Oct 23 10:04:28 2025 -0500 fix: update casing of fallback value from camel to snake commit 8d044bcc6ca1cfca4b2df886bc6ae78fc925a97d Author: Doug Stephen Date: Thu Oct 23 10:01:49 2025 -0500 fix: guard against iterating over potential nils commit d019056122fa737e70d857070a048c32eb65c5ae Author: zhou chengyu <2865135216@qq.com> Date: Thu Oct 23 13:26:53 2025 +0800 Added three WISTAR Smart Vertical Blind Motors Signed-off-by: zhou chengyu <2865135216@qq.com> commit 516472a6ba57b3da0b89e3e2a586508868c633de Merge: 4b220d6f 3ca004d2 Author: Steven Green Date: Wed Oct 22 12:57:54 2025 -0700 Merge pull request #2486 from SmartThingsCommunity/main Rolling up main to beta commit 4b220d6fcad549fd5ef9c431d2b2ec90231ecb6b Merge: eb2feacd c16770de Author: Steven Green Date: Wed Oct 22 11:01:05 2025 -0700 Merge pull request #2484 from SmartThingsCommunity/cherry-pick/10-22-25 Cherry-pick 10-22-25 commit c16770debb287f46f9645c121e118441fd118be7 Author: Steven Green Date: Mon Oct 20 15:36:59 2025 -0700 Merge pull request #2477 from SmartThingsCommunity/new_device/WWSTCERT-8285 WWSTCERT-8285 NodOn Zigbee Temperature and Humidity Sensor commit fc8f3237183e03c5df109a3141cc614f0ea72de4 Author: Steven Green Date: Fri Oct 17 13:10:33 2025 -0700 Merge pull request #2474 from SmartThingsCommunity/new_device/WWSTCERT-8293 WWSTCERT-8293 Meross Smart Wi-Fi Thermostat commit 0c9bb1df7f7417e44806732c617e23a23eae95a8 Author: Steven Green Date: Fri Oct 17 14:56:37 2025 -0700 Merge pull request #2475 from SmartThingsCommunity/revert/WWSTCERT-7351 Revert WWSTCERT-7351 commit eb2feacdb3b26b5ed4d7316467148341f9150a28 Merge: a2f59f96 07532ad8 Author: Douglas Stephen Date: Mon Oct 13 16:21:38 2025 -0500 Merge pull request #2464 from SmartThingsCommunity/main commit a2f59f96fee45727eb508034a10ed64bbdf29651 Merge: 39b6c3cc 8511a240 Author: Douglas Stephen Date: Mon Oct 13 14:30:56 2025 -0500 Merge pull request #2462 from SmartThingsCommunity/expedite-beta/2025-10-13-WWST-Zimi-Matter-Connect commit 8511a240d06b1fd6e07507f8d9cb69edcf219bab Author: Steven Green Date: Mon Oct 6 14:49:02 2025 -0700 WWSTCERT-8030 Zimi Matter Connect commit 39b6c3cc6ffa5c9671ffb765e5fd333eb34a137a Merge: b72adec2 eb5199c5 Author: Carter Swedal Date: Mon Oct 6 14:24:52 2025 -0500 Merge pull request #2451 from SmartThingsCommunity/main Beta channel release 10/6 commit b72adec261054ad15bc0708d78da16c494617e02 Merge: e016d66b 3f0cbbed Author: Carter Swedal Date: Mon Oct 6 13:47:25 2025 -0500 Merge pull request #2449 from SmartThingsCommunity/skip-beta-10-6 Skip beta 10-6 commit 3f0cbbed11300e4bd7d0e58210d19fc79daae9f5 Author: Harrison Carter Date: Mon Oct 6 12:17:34 2025 -0500 keep SUPPORTED_COMPONENT_CAPABILITIES table on driver commit 6529516248f7806e8479eb191801723d4c7d32c2 Author: Steven Green Date: Tue Sep 30 10:43:51 2025 -0700 WWSTCERT-8141 Decora Smart Wi-Fi ELV Dimmer WWSTCERT-8144 Decora Smart Wi-Fi 0-10V Dimmer WWSTCERT-8148 Decora Smart Wi-Fi (2nd Gen) Mini Plug-In Dimmer WWSTCERT-8151 Decora Smart Wi-Fi (2nd Gen) Tamper Resistant Outlet WWSTCERT-8154 Decora Smart Wi-Fi Mini Plug-In Outlet WWSTCERT-8157 Decora Smart Wi-Fi Outdoor Plug-In Switch WWSTCERT-8160 Decora Evolve Smart Switch Module WWSTCERT-8163 Decora Evolve Smart Dimmer Module commit 63af165d72276337a2771b76baade2bc9dfd6b38 Author: Steven Green Date: Mon Sep 29 14:56:02 2025 -0700 WWSTCERT-8129 Heiman Smart water leakage sensor commit e016d66b8acbb9a93145355f8b5858bb901ec5cd Merge: 1fc5cd3e 2c3c5952 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon Sep 29 15:11:19 2025 -0500 Merge pull request #2433 from SmartThingsCommunity/main rolling up main to beta 9/29/25 commit 1fc5cd3e291f212e3f273c0a24775f57f510acfe Merge: 04067bb8 0336026c Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon Sep 29 14:46:07 2025 -0500 Merge branch 'production' into beta commit 04067bb87b64947e7ffc4fa9e30ee9f3ebfffdc0 Merge: aca12888 925b0509 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Mon Sep 29 14:08:35 2025 -0500 Merge pull request #2432 from SmartThingsCommunity/cherry-pick/9-29-25 Cherry pick to beta 9-29-25 commit 925b0509f33884bb075cd0c44285fb25b87143d9 Author: Steven Green Date: Wed Sep 24 11:19:11 2025 -0700 WWSTCERT-8033 LIFX Everyday Smart Light 2-Pack (#2417) commit 47bf57b0c7fd12c6b553facbf69c09b28fbb17a6 Author: Zhongpei Ge Date: Fri Sep 19 18:07:44 2025 +0800 This commit fixes the issue of some devices "false alarm" after hub switch-over. We add check_latest state before sending initial event(open/unlocked/presense) for following drivers: - SmartThings/zigbee-button/src/aqara/init.lua - SmartThings/zigbee-button/src/dimming-remote/init.lua - SmartThings/zigbee-button/src/frient/init.lua - SmartThings/zigbee-button/src/init.lua - SmartThings/zigbee-button/src/iris/init.lua - SmartThings/zigbee-button/src/pushButton/init.lua - SmartThings/zigbee-button/src/zigbee-multi-button/ikea/TRADFRI_remote_control.lua - SmartThings/zigbee-button/src/zigbee-multi-button/ikea/init.lua - SmartThings/zigbee-button/src/zigbee-multi-button/init.lua - SmartThings/zigbee-button/src/zigbee-multi-button/robb/init.lua - SmartThings/zigbee-dimmer-remote/src/zigbee-accessory-dimmer/init.lua - SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/init.lua - SmartThings/zigbee-switch/src/aqara/multi-switch/init.lua - SmartThings/zigbee-switch/src/inovelli-vzm31-sn/init.lua - SmartThings/zigbee-switch/src/zigbee-dimming-light/init.lua - SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/init.lua - SmartThings/zigbee-window-treatment/src/aqara/init.lua - SmartThings/zigbee-window-treatment/src/aqara/roller-shade/init.lua - SmartThings/zwave-switch/src/eaton-anyplace-switch/init.lua - Unofficial/tuya-zigbee/src/button/init.lua - Unofficial/tuya-zigbee/src/button/meian-button/init.lua - Unofficial/tuya-zigbee/src/curtain/init.lua commit aca12888c9c12529939f909cecb2b4c225c87f97 Merge: 54d04e92 754f2a7a Author: Steven Green Date: Mon Sep 22 13:52:16 2025 -0700 Merge pull request #2421 from SmartThingsCommunity/main Rolling up main to beta commit 0336026c4c4e139c1b3eb7e9812162a364b3fb85 Merge: 63511492 77594aee Author: Steven Green Date: Mon Sep 22 13:06:32 2025 -0700 Merge pull request #2420 from SmartThingsCommunity/cherry-pick/9-22-25 Cherry pick to production 9/22/25 commit 77594aee6eac2a369026430252056caa2aa996cb Author: GAFfrient <96058156+GAFfrient@users.noreply.github.com> Date: Fri Sep 12 20:52:30 2025 +0200 WWSTCERT-7636 Frient humidity sensor (hmszb 120/hmszb 110) add support (#2356) * Add support for HMSZB-120/HMSZB-110 * According to @greens feedback. * Whitespace * Updated test message. * Fix info_changed part. * Whitespace. * Fix test. * Fix test 2 * Whitespace fix. --------- Co-authored-by: Marcin Krystian Tyminski <81477027+marcintyminski@users.noreply.github.com> commit 03133d9029ecac4f1943206aa8fc72fd31f446e5 Author: Cooper Towns Date: Thu Sep 11 12:55:30 2025 -0500 Update xCREAS Air Purifier fingerprint to static profile (#2390) commit 7a891bce3f87729d7792dc46e43901c82f680502 Author: NoahCornell Date: Tue Sep 2 09:54:11 2025 -0500 sonos: Update oauth_is_connected to oauth_app_connected commit b68520ebb99e6c9497b8053c0b90549bc97b4638 Author: NoahCornell Date: Thu Aug 21 10:51:10 2025 -0500 sonos: Wait for valid token before sending commands commit ad6a179137e7c01f47535729b64426490d6b2e1c Author: NoahCornell Date: Thu Aug 21 10:50:20 2025 -0500 sonos: remove token requests outside of the token refresher task commit 64dfbedfd0891a362aaf86595d6d8eeda4a93886 Author: NoahCornell Date: Thu Aug 21 10:48:21 2025 -0500 sonos: Add persistent token refresher task commit 1861e4ea0371150cada4b53ade2ab3338001bc28 Author: NoahCornell Date: Thu Aug 21 10:46:01 2025 -0500 sonos: Only return oauth token if oauth is connected commit f9e285909124e95cd59e1f16d1a6ddc9d0a9a8c0 Author: NoahCornell Date: Thu Aug 21 10:43:27 2025 -0500 sonos: Add bus to receive oauth endpoint app info events commit 9e61335ebc53049e7065e622d739dd2f2bee69bc Author: NoahCornell Date: Tue Aug 19 10:34:31 2025 -0500 sonos: Remove preemptive token refreshing. This does nothing without force refresh which could be problematic to use. commit e52978c78148986a9192e1ff6c3f93a05b694268 Author: Steven Green Date: Mon Sep 22 12:03:25 2025 -0700 Merge pull request #2419 from SmartThingsCommunity/cherry-pick/9-22-25 cherry pick to beta 9/22/25 commit 54d04e92086ba888b70cff605e9f35ef1bf93dd0 Merge: 1ff645fe bdec9a45 Author: Steven Green Date: Mon Sep 22 12:03:25 2025 -0700 Merge pull request #2419 from SmartThingsCommunity/cherry-pick/9-22-25 cherry pick to beta 9/22/25 commit bdec9a45df78dbf57ed1cf6794fe2abbff5ccf5a Author: Steven Green Date: Fri Sep 19 11:32:52 2025 -0700 Merge pull request #2410 from SmartThingsCommunity/new_device/WWSTCERT-8003 WWSTCERT-8003 Decora Smart Wi-Fi (2nd Gen) Switch commit 6351149246e35975360635714ec8582b2d36bfe4 Merge: 0aa75db6 0341df26 Author: Steven Green Date: Fri Sep 19 12:32:17 2025 -0700 Merge pull request #2415 from SmartThingsCommunity/cherry-pick/new_pipeline Cherry-pick new pipeline changes to production commit 0341df26fd98181896f4b03f18e0cbb04e6710e5 Author: Steven Green Date: Fri Sep 19 11:09:38 2025 -0700 Merge pull request #2413 from SmartThingsCommunity/np/jenkins-dry-run Update dry run boolean logic to handle boolean env variable commit 660809dd0e232c3700224c08592b71288776b3a6 Author: Nick Peterson Date: Thu Sep 18 18:24:55 2025 +0000 Refactor Jenkins Pipeline (#2393) commit 1ff645fe0495d1117e587d383f6199a1093a9366 Merge: b6a52bc0 ffe4bc9e Author: Steven Green Date: Fri Sep 19 12:14:19 2025 -0700 Merge pull request #2414 from SmartThingsCommunity/cherry-pick/new_pipeline Cherry-pick new deploy pipeline to all deploy branches commit ffe4bc9e4bea047d598aedd5165e4b4c693a825a Author: Steven Green Date: Fri Sep 19 11:09:38 2025 -0700 Merge pull request #2413 from SmartThingsCommunity/np/jenkins-dry-run Update dry run boolean logic to handle boolean env variable commit 6fe2a8e4e8313386587a8ec231bb217954b06f63 Author: Nick Peterson Date: Thu Sep 18 18:24:55 2025 +0000 Refactor Jenkins Pipeline (#2393) commit 0aa75db6f144a42ae35cda64303e79d637167090 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed Sep 17 13:38:02 2025 -0500 Prod hotfix: matter rvc UX updates and matter lock subdriver Aliro support (#2406) * Updating matter rvc ux (#2343) Signed-off-by: HunsupJung * Update matter-rvc driver (#2400) - Replace Embedded device configuration with Device presentation to support translation - If selected_area is empty, selecting all areas Signed-off-by: HunsupJung * Update new-matter-lock driver to support Aliro feature (#2344) Signed-off-by: Hunsup Jung Co-authored-by: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> --------- Signed-off-by: HunsupJung Signed-off-by: Hunsup Jung Co-authored-by: HunsupJung <59987061+HunsupJung@users.noreply.github.com> commit b6a52bc0d766604b4db10dcb5289569f53a6f269 Author: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Wed Sep 17 12:43:54 2025 -0500 Beta hotfix: matter rvc ux upgrade, matter lock aliro support (#2403) * Updating matter rvc ux (#2343) Signed-off-by: HunsupJung * Update matter-rvc driver (#2400) - Replace Embedded device configuration with Device presentation to support translation - If selected_area is empty, selecting all areas Signed-off-by: HunsupJung * Update new-matter-lock driver to support Aliro feature (#2344) Signed-off-by: Hunsup Jung Co-authored-by: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> --------- Signed-off-by: HunsupJung Signed-off-by: Hunsup Jung Co-authored-by: HunsupJung <59987061+HunsupJung@users.noreply.github.com> commit de248078754f39e9a201442fdf2ae1fa458f7aa9 Merge: 1b6971ac a7a2980d Author: Steven Green Date: Mon Sep 15 14:10:31 2025 -0700 Merge pull request #2398 from SmartThingsCommunity/main Rolling up main to beta commit 217e8671226bdbce66996dfe84de1d69b2e4fffb Merge: 8dfbcd02 1b6971ac Author: Steven Green Date: Mon Sep 15 13:22:56 2025 -0700 Merge pull request #2396 from SmartThingsCommunity/beta Rolling up Beta to Production commit 1b6971ac44b52e31ae96aaac1751a8b097e19205 Merge: 46721227 582d92fc Author: Steven Green Date: Mon Sep 15 13:08:59 2025 -0700 Cherry pick to beta 9/15/25 (#2395) * WWSTCERT-7632 LUX TQ1 Smart Thermostat (#2361) * WWSTCERT-7632 LUX TQ1 Smart Thermostat * change profile * Revert "change profile" This reverts commit 4a2e42b1fc7a87b3df7bd104c08a2841c8f7e386. * WWSTCERT-7844 Feature/matter britzyhub (#2189) --------- Co-authored-by: KyuminAhn <46492097+KyuminAhn@users.noreply.github.com> commit 582d92fc348b85d0911ebd4ca1c908cf42c4b1e4 Author: KyuminAhn <46492097+KyuminAhn@users.noreply.github.com> Date: Mon Sep 15 10:13:39 2025 +0900 WWSTCERT-7844 Feature/matter britzyhub (#2189) commit 09b90d930a94babeb4c87b82ff7d496b03e7ef44 Author: Steven Green Date: Fri Sep 12 13:40:00 2025 -0700 WWSTCERT-7632 LUX TQ1 Smart Thermostat (#2361) * WWSTCERT-7632 LUX TQ1 Smart Thermostat * change profile * Revert "change profile" This reverts commit 4a2e42b1fc7a87b3df7bd104c08a2841c8f7e386. commit 467212272114907061e3f6815c3924a46cbedfb3 Merge: 0f31ab13 ad066d6e Author: Steven Green Date: Wed Sep 10 12:30:14 2025 -0700 Merge pull request #2388 from SmartThingsCommunity/main Rolling up main to beta commit 8dfbcd0267e486e6a7e6c46d7360262d52869b49 Merge: becf2cf7 0f31ab13 Author: Steven Green Date: Wed Sep 10 11:59:55 2025 -0700 Merge pull request #2387 from SmartThingsCommunity/beta Rolling up beta to production commit d7d54ddb6fd9bab919e07cabae8d37e871a46aee Author: Jeron Aldaron Lau Date: Tue May 13 17:33:11 2025 -0500 Fix broken link for contributing code guidelines commit becf2cf7271d31f6da8327ff8c71dfea24cc4d4a Merge: bf500b38 6f517d59 Author: Carter Swedal Date: Mon Aug 25 14:45:11 2025 -0500 Merge pull request #2363 from SmartThingsCommunity/beta Beta commit bf500b380f3e4e5746207ff29d6b9f1cbf99a61d Merge: c8672eb8 487bcc90 Author: Steven Green Date: Mon Aug 18 11:46:01 2025 -0700 Merge pull request #2348 from SmartThingsCommunity/beta Rolling up beta to production commit c8672eb807abad922c1aba6f2fb7174976a4f296 Merge: 00e492a7 f143d31a Author: Tom Manley Date: Tue Aug 12 14:55:55 2025 -0500 Merge pull request #2335 from SmartThingsCommunity/hotfix-to-prod-revert-CHAD-16109 Hot fix to prod: Revert "philips-hue: Fix bug where `added` handler fails for migrated… commit f143d31aec5edc43c20a4074ac04485de7b7f0ec Author: NoahCornell Date: Tue Aug 12 10:26:03 2025 -0500 Revert "philips-hue: Fix bug where `added` handler fails for migrated devices" This reverts commit 2533e079be45a5b4996fbc310f89d5863c2bddd4. commit 00e492a76f538f4345cc15d12854109c85defbb7 Merge: f89afe9b 54e7119c Author: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Mon Aug 11 15:11:03 2025 -0500 Merge pull request #2331 from SmartThingsCommunity/beta Rolling up beta to production 8/11/25 commit f89afe9b5b9e5b9a55f98b86d6b4871ddd96a634 Merge: a2d6b11f 4aa644b3 Author: Cooper Towns Date: Tue Aug 5 15:40:45 2025 -0500 Merge pull request #2322 from SmartThingsCommunity/prod-hotfix/matter-switch-energy-fix Prod hotfix matter switch energy changes commit 4aa644b3a6f691a89c3ac250e6f28edd25287ae5 Author: Nick DeBoom Date: Tue Aug 5 10:52:25 2025 -0500 Set Energy Management Endpoint field on init commit 014bfdaebad8d492b59a08bd18b3df69ad1ee401 Author: Nick DeBoom Date: Tue Aug 5 10:52:06 2025 -0500 Revert "add greater energy profiling logic for switches (#2199)" This reverts commit f3642cf13230d9ece56740306b1d74b7b7c6e42c. commit a2d6b11f6563c2017e7e3681e19a0326a2731f9a Merge: 87a1d1d4 33b82581 Author: Steven Green Date: Mon Aug 4 12:57:49 2025 -0700 Merge pull request #2315 from SmartThingsCommunity/beta Rolling up beta to production commit 87a1d1d452873ac775b97e82696035d2f0a422be Merge: ccc44ef0 8996666a Author: Cooper Towns Date: Mon Jul 28 15:44:21 2025 -0500 Merge pull request #2290 from SmartThingsCommunity/beta rolling up beta to producton commit ccc44ef066bd695e7e76ffa627cc40e8531a1812 Merge: f1337ca3 da1deb41 Author: Steven Green Date: Mon Jul 21 13:30:52 2025 -0700 Merge pull request #2275 from SmartThingsCommunity/beta Rolling up beta to production for deploy commit f1337ca3076007ecc60f10c83f43edcc202f1f89 Merge: e8e5d762 f32973d5 Author: Carter Swedal Date: Mon Jul 14 13:46:19 2025 -0500 Merge pull request #2255 from SmartThingsCommunity/beta Production release commit e8e5d762b41bb3c3ae1e9bc26503956a10b28158 Merge: 47d38e51 370e2c32 Author: Douglas Stephen Date: Wed Jul 9 10:53:14 2025 -0500 Merge pull request #2242 from SmartThingsCommunity/hotfix/sonos-recursion-oom-crash commit 370e2c32dc03b119d94a60ec8e38a572b4ec9f81 Author: NoahCornell Date: Tue Jul 1 15:52:02 2025 -0500 sonos: fix infinite recursive reconnect tasks commit 47d38e515532d5014261883e636f0e451afe7bc1 Merge: 159141ed bf5bfd46 Author: Carter Swedal Date: Fri Jun 27 10:20:07 2025 -0500 Merge pull request #2223 from SmartThingsCommunity/prod-hotfix Prod hotfix commit bf5bfd46a9e79dd47456414ad9d93d7533d58ea7 Author: Doug Stephen Date: Wed Jun 25 17:22:18 2025 -0500 fix: Another nil index along an error handling path commit 22128bea0b933f34d4e023bd8193d3056b3ea983 Author: cjswedes Date: Wed Jun 25 11:55:24 2025 -0500 Mitigate deserialization errors due to hub 0.57 FW bug 0.57 FW has an extra byte as the first byte of zdo message body payloads and is missing the last byte of the body of the payload. This adds a mitigation in the ZdoMessageBody deserialization code which overrides the Lua library function that corrects the off by 1 error and inserts a default last byte of 0x01 for every Zdo message payload. commit 159141ed542b1c5892716465fe665fd000301b40 Merge: e7b40a14 2a2709e4 Author: Chris Baumler Date: Wed Jun 25 13:03:41 2025 -0500 Merge pull request #2219 from SmartThingsCommunity/prod-hotfix/sonos-crashes Hotfix Sonos Crash Fixes to Production commit 2a2709e4f4bc58fa87482dbc7c2b38e9c5cee365 Author: Chris Baumler Date: Wed Jun 25 12:26:52 2025 -0500 Merge pull request #2217 from SmartThingsCommunity/beta-hotfix/sonos-crashes Hotfix Sonos Crashes commit e7b40a14eb0eb766c8b57f84908e5232a55de356 Merge: a488e180 d0d0259f Author: Chris Baumler Date: Mon Jun 23 17:44:16 2025 -0500 Merge pull request #2211 from SmartThingsCommunity/beta Rolling up beta into production --- .gitignore | 2 + README.md | 2 +- .../ActivatedCarbonFilterMonitoring/init.lua | 4 + .../server/attributes/AttributeList.lua | 17 +- .../server/attributes/ChangeIndication.lua | 17 +- .../types/init.lua | 3 + .../src/DishwasherAlarm/init.lua | 4 + .../src/DishwasherAlarm/types/init.lua | 3 + .../src/DishwasherMode/init.lua | 4 + .../src/DishwasherMode/types/init.lua | 3 + .../src/HepaFilterMonitoring/init.lua | 4 + .../server/attributes/AttributeList.lua | 17 +- .../server/attributes/ChangeIndication.lua | 17 +- .../src/HepaFilterMonitoring/types/init.lua | 3 + .../src/LaundryWasherControls/init.lua | 4 + .../src/LaundryWasherControls/types/init.lua | 3 + .../src/LaundryWasherMode/init.lua | 4 + .../src/LaundryWasherMode/types/init.lua | 3 + .../src/MicrowaveOvenControl/init.lua | 6 +- .../src/MicrowaveOvenControl/types/init.lua | 3 + .../src/MicrowaveOvenMode/init.lua | 4 + .../src/MicrowaveOvenMode/types/init.lua | 3 + .../src/OperationalState/init.lua | 4 + .../src/OperationalState/types/init.lua | 3 + .../matter-appliance/src/OvenMode/init.lua | 4 + .../src/OvenMode/types/init.lua | 3 + .../src/RefrigeratorAlarm/init.lua | 4 + .../src/RefrigeratorAlarm/types/init.lua | 3 + .../init.lua | 4 + .../types/init.lua | 3 + .../src/TemperatureControl/init.lua | 4 + .../src/TemperatureControl/types/init.lua | 3 + .../matter-appliance/src/common-utils.lua | 16 +- .../src/embedded-cluster-utils.lua | 16 +- .../SmartThings/matter-appliance/src/init.lua | 25 +- .../src/lazy_load_subdriver.lua | 14 + .../src/matter-cook-top/can_handle.lua | 21 + .../src/matter-cook-top/init.lua | 170 +- .../src/matter-dishwasher/can_handle.lua | 17 + .../src/matter-dishwasher/init.lua | 29 +- .../src/matter-extractor-hood/can_handle.lua | 16 + .../src/matter-extractor-hood/init.lua | 29 +- .../src/matter-laundry/can_handle.lua | 19 + .../src/matter-laundry/init.lua | 33 +- .../src/matter-microwave-oven/can_handle.lua | 16 + .../src/matter-microwave-oven/init.lua | 29 +- .../src/matter-oven/can_handle.lua | 14 + .../matter-appliance/src/matter-oven/init.lua | 26 +- .../src/matter-refrigerator/can_handle.lua | 16 + .../src/matter-refrigerator/init.lua | 30 +- .../matter-appliance/src/sub_drivers.lua | 14 + .../src/test/test_cook_top.lua | 18 +- .../src/test/test_dishwasher.lua | 16 +- .../src/test/test_extractor_hood.lua | 16 +- .../src/test/test_laundry_dryer.lua | 16 +- .../src/test/test_laundry_washer.lua | 16 +- .../src/test/test_matter_appliance_rpc_5.lua | 16 +- .../src/test/test_microwave_oven.lua | 16 +- .../matter-appliance/src/test/test_oven.lua | 16 +- .../src/test/test_refrigerator.lua | 16 +- .../src/test/test_battery_storage.lua | 8 +- .../src/test/test_solar_power.lua | 8 +- .../SmartThings/matter-lock/fingerprints.yml | 26 + .../profiles/base-lock-batteryLevel.yml | 1 + .../profiles/base-lock-nobattery.yml | 1 + .../matter-lock/profiles/base-lock.yml | 1 + .../profiles/lock-lockalarm-batteryLevel.yml | 1 + .../profiles/lock-lockalarm-nobattery.yml | 1 + .../matter-lock/profiles/lock-lockalarm.yml | 1 + .../lock-nocodes-notamper-batteryLevel.yml | 1 + .../profiles/lock-nocodes-notamper.yml | 1 + .../lock-without-codes-batteryLevel.yml | 1 + .../profiles/lock-without-codes-nobattery.yml | 1 + .../profiles/lock-without-codes.yml | 1 + .../nonfunctional-lock-batteryLevel.yml | 1 + .../profiles/nonfunctional-lock.yml | 1 + .../matter-lock/src/new-matter-lock/init.lua | 8 +- .../matter-sensor/fingerprints.yml | 53 + .../matter-motion-battery-illuminance.yml | 1 + .../profiles/matter-motion-battery.yml | 1 + ...matter-motion-batteryLevel-illuminance.yml | 1 + .../profiles/matter-motion-batteryLevel.yml | 1 + .../profiles/smoke-temp-humidity-battery.yml | 25 + .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../src/PressureMeasurement/types/init.lua | 35 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../src/air-quality-sensor/init.lua | 677 ----- .../AirQuality/init.lua | 7 +- .../server/attributes/AcceptedCommandList.lua | 3 + .../server/attributes/AirQuality.lua | 5 +- .../server/attributes/AttributeList.lua | 3 + .../server/attributes/EventList.lua | 3 + .../AirQuality/server/attributes/init.lua | 5 +- .../AirQuality/types/AirQualityEnum.lua | 3 + .../AirQuality/types/Feature.lua | 3 + .../AirQuality/types/init.lua | 5 +- .../BooleanStateConfiguration/init.lua | 4 +- .../attributes/CurrentSensitivityLevel.lua | 3 + .../attributes/DefaultSensitivityLevel.lua | 3 + .../server/attributes/SensorFault.lua | 5 +- .../attributes/SupportedSensitivityLevels.lua | 3 + .../server/attributes/init.lua | 5 +- .../types/Feature.lua | 3 + .../types/SensorFaultBitmap.lua | 3 + .../BooleanStateConfiguration/types/init.lua | 5 +- .../init.lua | 7 +- .../server/attributes/LevelValue.lua | 7 +- .../server/attributes/MeasuredValue.lua | 5 +- .../server/attributes/MeasurementUnit.lua | 7 +- .../server/attributes/init.lua | 5 +- .../init.lua | 7 +- .../server/attributes/LevelValue.lua | 7 +- .../server/attributes/MeasuredValue.lua | 5 +- .../server/attributes/MeasurementUnit.lua | 7 +- .../server/attributes/init.lua | 5 +- .../ConcentrationMeasurement/init.lua | 7 +- .../server/attributes/LevelValue.lua | 5 +- .../server/attributes/MeasuredValue.lua | 3 + .../server/attributes/MeasurementUnit.lua | 5 +- .../server/attributes/init.lua | 5 +- .../types/Feature.lua | 3 + .../types/LevelValueEnum.lua | 3 + .../types/MeasurementUnitEnum.lua | 3 + .../ConcentrationMeasurement/types/init.lua | 5 +- .../init.lua | 7 +- .../server/attributes/LevelValue.lua | 7 +- .../server/attributes/MeasuredValue.lua | 5 +- .../server/attributes/MeasurementUnit.lua | 7 +- .../server/attributes/init.lua | 5 +- .../init.lua | 7 +- .../server/attributes/LevelValue.lua | 7 +- .../server/attributes/MeasuredValue.lua | 5 +- .../server/attributes/MeasurementUnit.lua | 7 +- .../server/attributes/init.lua | 5 +- .../OzoneConcentrationMeasurement/init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../Pm10ConcentrationMeasurement/init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../Pm1ConcentrationMeasurement/init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../Pm25ConcentrationMeasurement/init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../PressureMeasurement/init.lua | 23 +- .../server/attributes/AcceptedCommandList.lua | 17 +- .../server/attributes/AttributeList.lua | 17 +- .../server/attributes/EventList.lua | 17 +- .../server/attributes/MaxMeasuredValue.lua | 17 +- .../server/attributes/MaxScaledValue.lua | 17 +- .../server/attributes/MeasuredValue.lua | 17 +- .../server/attributes/MinMeasuredValue.lua | 17 +- .../server/attributes/MinScaledValue.lua | 17 +- .../server/attributes/Scale.lua | 17 +- .../server/attributes/ScaledTolerance.lua | 17 +- .../server/attributes/ScaledValue.lua | 17 +- .../server/attributes/Tolerance.lua | 17 +- .../server/attributes/init.lua | 19 +- .../server/commands/init.lua | 19 +- .../server/events/init.lua | 19 +- .../PressureMeasurement/types/Feature.lua | 17 +- .../PressureMeasurement/types/init.lua | 24 + .../RadonConcentrationMeasurement/init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../SmokeCoAlarm/init.lua | 9 +- .../server/attributes/BatteryAlert.lua | 5 +- .../server/attributes/COState.lua | 5 +- .../server/attributes/HardwareFaultAlert.lua | 3 + .../attributes/SmokeSensitivityLevel.lua | 5 +- .../server/attributes/SmokeState.lua | 5 +- .../server/attributes/TestInProgress.lua | 3 + .../SmokeCoAlarm/server/attributes/init.lua | 5 +- .../server/commands/SelfTestRequest.lua | 3 + .../SmokeCoAlarm/server/commands/init.lua | 5 +- .../SmokeCoAlarm/types/AlarmStateEnum.lua | 3 + .../SmokeCoAlarm/types/Feature.lua | 3 + .../SmokeCoAlarm/types/SensitivityEnum.lua | 3 + .../SmokeCoAlarm/types/init.lua | 5 +- .../init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../SmartThings/matter-sensor/src/init.lua | 626 +--- .../matter-sensor/src/lazy_load_subdriver.lua | 14 + .../sensor_handlers/attribute_handlers.lua | 204 ++ .../src/sensor_utils/device_configuration.lua | 124 + .../embedded_cluster_utils.lua} | 29 +- .../matter-sensor/src/sensor_utils/fields.lua | 50 + .../matter-sensor/src/sensor_utils/utils.lua | 24 + .../matter-sensor/src/sub_drivers.lua | 10 + .../attribute_handlers.lua | 74 + .../device_configuration.lua | 89 + .../air_quality_sensor_utils/fields.lua | 180 ++ .../legacy_device_configuration.lua | 89 + .../air_quality_sensor_utils/utils.lua | 80 + .../air_quality_sensor/can_handle.lua | 17 + .../sub_drivers/air_quality_sensor/init.lua | 154 + .../bosch_button_contact/can_handle.lua | 16 + .../bosch_button_contact}/init.lua | 32 +- .../sub_drivers/smoke_co_alarm/can_handle.lua | 17 + .../smoke_co_alarm}/init.lua | 197 +- .../test/test_matter_air_quality_sensor.lua | 143 +- ...test_matter_air_quality_sensor_modular.lua | 234 +- .../test/test_matter_bosch_button_contact.lua | 15 +- .../src/test/test_matter_flow_sensor.lua | 15 +- .../test/test_matter_freeze_leak_sensor.lua | 21 +- .../src/test/test_matter_pressure_sensor.lua | 33 +- .../src/test/test_matter_rain_sensor.lua | 20 +- .../src/test/test_matter_sensor.lua | 15 +- .../src/test/test_matter_sensor_battery.lua | 15 +- .../test/test_matter_sensor_featuremap.lua | 15 +- .../src/test/test_matter_sensor_rpc.lua | 15 +- .../src/test/test_matter_smoke_co_alarm.lua | 20 +- .../test_matter_smoke_co_alarm_battery.lua | 24 +- .../matter-switch/fingerprints.yml | 1024 ++++++- .../profiles/3-button-motion.yml | 26 + .../profiles/6-button-motion.yml | 44 + .../matter-switch/profiles/camera.yml | 156 + .../matter-switch/profiles/fan-modular.yml | 17 + .../matter-switch/profiles/ikea-scroll.yml | 29 + .../light-energy-powerConsumption.yml | 16 + .../light-level-energy-powerConsumption.yml | 22 + .../profiles/light-level-power.yml | 20 + .../matter-switch/profiles/light-power.yml | 14 + .../src/embedded_clusters/Descriptor/init.lua | 53 + .../server/attributes/PartsList.lua | 78 + .../Descriptor/server/attributes/init.lua | 27 + .../ElectricalEnergyMeasurement/init.lua | 3 + .../attributes/CumulativeEnergyImported.lua | 3 + .../attributes/PeriodicEnergyImported.lua | 3 + .../server/attributes/init.lua | 3 + .../types/EnergyMeasurementStruct.lua | 3 + .../types/Feature.lua | 3 + .../types/init.lua | 3 + .../ElectricalPowerMeasurement/init.lua | 3 + .../server/attributes/ActivePower.lua | 3 + .../server/attributes/init.lua | 3 + .../embedded_clusters/PowerTopology/init.lua | 55 + .../server/attributes/AvailableEndpoints.lua | 72 + .../PowerTopology/server/attributes/init.lua | 23 + .../PowerTopology/types/Feature.lua | 32 + .../PowerTopology/types/init.lua | 14 + .../ValveConfigurationAndControl/init.lua | 3 + .../server/attributes/CurrentLevel.lua | 3 + .../server/attributes/CurrentState.lua | 3 + .../server/attributes/init.lua | 3 + .../server/commands/Close.lua | 3 + .../server/commands/Open.lua | 3 + .../server/commands/init.lua | 3 + .../types/Feature.lua | 3 + .../types/ValveStateEnum.lua | 3 + .../types/init.lua | 3 + .../SmartThings/matter-switch/src/init.lua | 164 +- .../src/sub_drivers/aqara_cube/can_handle.lua | 14 + .../src/sub_drivers/aqara_cube/init.lua | 27 +- .../camera_handlers/attribute_handlers.lua | 419 +++ .../camera_handlers/capability_handlers.lua | 356 +++ .../camera/camera_handlers/event_handlers.lua | 30 + .../camera_utils/device_configuration.lua | 283 ++ .../camera/camera_utils/fields.lua | 48 + .../sub_drivers/camera/camera_utils/utils.lua | 306 ++ .../src/sub_drivers/camera/can_handle.lua | 16 + .../src/sub_drivers/camera/init.lua | 207 ++ .../src/sub_drivers/eve_energy/can_handle.lua | 18 + .../src/sub_drivers/eve_energy/init.lua | 32 +- .../sub_drivers/ikea_scroll/can_handle.lua | 11 + .../src/sub_drivers/ikea_scroll/init.lua | 50 + .../scroll_utils/device_configuration.lua | 35 + .../ikea_scroll/scroll_utils/fields.lua | 21 + .../ikea_scroll/scroll_utils/utils.lua | 26 + .../third_reality_mk1/can_handle.lua | 14 + .../sub_drivers/third_reality_mk1/init.lua | 31 +- .../attribute_handlers.lua | 189 +- .../capability_handlers.lua | 44 +- .../event_handlers.lua | 19 +- .../{utils => switch_utils}/color_utils.lua | 15 +- .../src/switch_utils/device_configuration.lua | 257 ++ .../embedded_cluster_utils.lua | 15 +- .../matter-switch/src/switch_utils/fields.lua | 185 ++ .../matter-switch/src/switch_utils/utils.lua | 501 ++++ .../test/test_aqara_climate_sensor_w100.lua | 16 +- .../src/test/test_aqara_cube.lua | 3 + .../src/test/test_aqara_light_switch_h2.lua | 41 +- .../src/test/test_electrical_sensor_set.lua | 738 +++++ ...or.lua => test_electrical_sensor_tree.lua} | 200 +- .../src/test/test_eve_energy.lua | 189 +- .../src/test/test_ikea_scroll.lua | 224 ++ .../test/test_light_illuminance_motion.lua | 36 +- .../src/test/test_matter_bridge.lua | 30 +- .../src/test/test_matter_button.lua | 4 + .../src/test/test_matter_camera.lua | 1889 ++++++++++++ .../src/test/test_matter_light_fan.lua | 65 +- .../src/test/test_matter_multi_button.lua | 13 +- .../test/test_matter_multi_button_motion.lua | 769 +++++ .../test_matter_multi_button_switch_mcd.lua | 236 +- .../src/test/test_matter_switch.lua | 208 +- .../test/test_matter_switch_device_types.lua | 151 +- .../src/test/test_matter_water_valve.lua | 15 +- .../src/test/test_multi_switch_mcd.lua | 15 +- .../test_multi_switch_parent_child_lights.lua | 78 +- .../test_multi_switch_parent_child_plugs.lua | 423 ++- .../src/test/test_third_reality_mk1.lua | 15 +- .../src/utils/device_configuration.lua | 273 -- .../matter-switch/src/utils/switch_fields.lua | 261 -- .../matter-switch/src/utils/switch_utils.lua | 244 -- .../matter-thermostat/fingerprints.yml | 16 + .../profiles/thermostat-modular.yml | 3 + .../server/attributes/AttributeList.lua | 76 - .../server/attributes/AcceptedCommandList.lua | 75 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../attributes/CumulativeEnergyExported.lua | 68 - .../attributes/PeriodicEnergyExported.lua | 68 - .../events/CumulativeEnergyMeasured.lua | 108 - .../server/events/PeriodicEnergyMeasured.lua | 108 - .../server/events/init.lua | 25 - .../types/Feature.lua | 116 - .../src/ElectricalPowerMeasurement/init.lua | 94 - .../types/Feature.lua | 138 - .../ElectricalPowerMeasurement/types/init.lua | 15 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../server/attributes/AttributeList.lua | 75 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../server/attributes/LevelValue.lua | 41 - .../server/attributes/MeasuredValue.lua | 56 - .../server/attributes/MeasurementUnit.lua | 45 - .../ActivatedCarbonFilterMonitoring/init.lua | 9 +- .../server/attributes/ChangeIndication.lua | 5 +- .../server/attributes/Condition.lua | 5 +- .../server/attributes/init.lua | 5 +- .../server/commands/ResetCondition.lua | 3 + .../server/commands/init.lua | 5 +- .../types/ChangeIndicationEnum.lua | 3 + .../types/Feature.lua | 3 + .../types/init.lua | 5 +- .../AirQuality/init.lua | 7 +- .../server/attributes/AcceptedCommandList.lua | 3 + .../server/attributes/AirQuality.lua | 5 +- .../server/attributes/AttributeList.lua | 3 + .../server/attributes/EventList.lua | 3 + .../AirQuality/server/attributes/init.lua | 5 +- .../AirQuality/types/AirQualityEnum.lua | 3 + .../AirQuality/types/Feature.lua | 3 + .../AirQuality/types/init.lua | 5 +- .../init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../ConcentrationMeasurement/init.lua | 7 +- .../server/attributes/LevelValue.lua | 5 +- .../server/attributes/MeasuredValue.lua | 3 + .../server/attributes/MeasurementUnit.lua | 5 +- .../server/attributes/init.lua | 5 +- .../types/Feature.lua | 3 + .../types/LevelValueEnum.lua | 3 + .../types/MeasurementUnitEnum.lua | 3 + .../ConcentrationMeasurement/types/init.lua | 5 +- .../ElectricalEnergyMeasurement/init.lua | 30 +- .../attributes/CumulativeEnergyImported.lua | 5 +- .../attributes/PeriodicEnergyImported.lua | 5 +- .../server/attributes/init.lua | 5 +- .../types/EnergyMeasurementStruct.lua | 3 + .../types/Feature.lua | 31 + .../types/init.lua | 5 +- .../ElectricalPowerMeasurement/init.lua | 44 + .../server/attributes/ActivePower.lua | 4 +- .../server/attributes/init.lua | 5 +- .../init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../HepaFilterMonitoring/init.lua | 9 +- .../server/attributes/ChangeIndication.lua | 5 +- .../server/attributes/Condition.lua | 3 + .../server/attributes/init.lua | 5 +- .../server/commands/ResetCondition.lua | 3 + .../server/commands/init.lua | 5 +- .../types/ChangeIndicationEnum.lua | 3 + .../HepaFilterMonitoring}/types/Feature.lua | 3 + .../HepaFilterMonitoring/types/init.lua | 5 +- .../init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../OzoneConcentrationMeasurement/init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../Pm10ConcentrationMeasurement/init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../Pm1ConcentrationMeasurement/init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../Pm25ConcentrationMeasurement/init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../RadonConcentrationMeasurement/init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../init.lua | 7 +- .../server/attributes/LevelValue.lua | 44 + .../server/attributes/MeasuredValue.lua | 59 + .../server/attributes/MeasurementUnit.lua | 48 + .../server/attributes/init.lua | 5 +- .../WaterHeaterMode/init.lua | 9 +- .../server/attributes/CurrentMode.lua | 4 +- .../server/attributes/SupportedModes.lua | 5 +- .../server/attributes/init.lua | 5 +- .../server/commands/ChangeToMode.lua | 3 + .../WaterHeaterMode/server/commands/init.lua | 5 +- .../WaterHeaterMode/types/Feature.lua | 3 + .../types/ModeOptionStruct.lua | 5 +- .../WaterHeaterMode/types/ModeTag.lua | 3 + .../WaterHeaterMode/types/ModeTagStruct.lua | 3 + .../WaterHeaterMode/types/init.lua | 5 +- .../matter-thermostat/src/init.lua | 2558 +++-------------- .../src/test/test_matter_air_purifier.lua | 42 +- .../test/test_matter_air_purifier_api9.lua | 42 +- .../test/test_matter_air_purifier_modular.lua | 45 +- .../src/test/test_matter_fan.lua | 16 +- .../src/test/test_matter_heat_pump.lua | 31 +- .../src/test/test_matter_room_ac.lua | 27 +- .../src/test/test_matter_room_ac_modular.lua | 19 +- .../src/test/test_matter_thermo_battery.lua | 19 +- .../test/test_matter_thermo_featuremap.lua | 24 +- ...st_matter_thermo_multiple_device_types.lua | 55 +- .../test_matter_thermo_setpoint_limits.lua | 18 +- ...test_matter_thermo_setpoint_limits_rpc.lua | 21 +- .../src/test/test_matter_thermostat.lua | 31 +- ...est_matter_thermostat_composed_bridged.lua | 21 +- .../test/test_matter_thermostat_modular.lua | 19 +- .../src/test/test_matter_thermostat_rpc5.lua | 22 +- .../src/test/test_matter_water_heater.lua | 28 +- .../attribute_handlers.lua | 661 +++++ .../capability_handlers.lua | 256 ++ .../thermostat_utils/device_configuration.lua | 331 +++ .../embedded_cluster_utils.lua} | 37 +- .../src/thermostat_utils/fields.lua | 238 ++ .../legacy_device_configuration.lua | 259 ++ .../src/thermostat_utils/utils.lua | 203 ++ .../matter-window-covering/fingerprints.yml | 51 + .../matter-window-covering/src/init.lua | 29 +- .../src/test/test_matter_window_covering.lua | 3 - .../handlers/lifecycle_handlers/button.lua | 19 + .../SmartThings/philips-hue/src/hue/api.lua | 10 +- .../philips-hue/src/utils/grouped_utils.lua | 2 +- .../sonos/src/api/event_handlers.lua | 2 +- .../sonos/src/api/sonos_connection.lua | 17 +- .../sonos/src/api/sonos_ssdp_discovery.lua | 8 +- .../sonos/src/api/sonos_websocket_router.lua | 16 +- drivers/SmartThings/sonos/src/cosock/bus.lua | 4 +- .../sonos/src/lifecycle_handlers.lua | 1 + .../SmartThings/sonos/src/sonos_driver.lua | 84 +- drivers/SmartThings/sonos/src/sonos_state.lua | 11 +- drivers/SmartThings/sonos/src/ssdp.lua | 6 +- drivers/SmartThings/sonos/src/utils.lua | 4 +- .../zigbee-bed/src/shus-mattress/init.lua | 6 +- .../src/test/test_shus_mattress.lua | 50 +- .../zigbee-button/fingerprints.yml | 24 +- .../profiles/aqara-double-buttons-mode.yml | 35 + .../profiles/aqara-double-buttons.yml | 32 + .../profiles/aqara-single-button-mode.yml | 17 + .../profiles/one-button-batteryLevel.yml | 14 + .../zigbee-button/src/aqara/init.lua | 227 +- .../src/test/test_aqara_button.lua | 213 +- .../src/ClimaxTechnology/can_handle.lua | 15 + .../src/ClimaxTechnology/fingerprints.lua | 9 + .../src/ClimaxTechnology/init.lua | 31 +- .../src/init.lua | 18 +- .../src/lazy_load_subdriver.lua | 15 + .../src/sub_drivers.lua | 10 + ...test_climax_technology_carbon_monoxide.lua | 15 +- .../src/test/test_zigbee_carbon_monoxide.lua | 15 +- .../zigbee-dimmer-remote/src/init.lua | 32 +- .../src/lazy_load_subdriver.lua | 15 + .../zigbee-dimmer-remote/src/sub_drivers.lua | 11 + .../src/test/test_zigbee_accessory_dimmer.lua | 2 +- .../zigbee-accessory-dimmer/can_handle.lua | 14 + .../zigbee-accessory-dimmer/fingerprints.lua | 9 + .../src/zigbee-accessory-dimmer/init.lua | 30 +- .../CentraliteSystems/can_handle.lua | 15 + .../CentraliteSystems/fingerprints.lua | 8 + .../CentraliteSystems/init.lua | 30 +- .../IKEAofSweden/can_handle.lua | 15 + .../IKEAofSweden/fingerprints.lua | 8 + .../IKEAofSweden/init.lua | 30 +- .../can_handle.lua | 15 + .../fingerprints.lua | 10 + .../zigbee-battery-accessory-dimmer/init.lua | 34 +- .../sengled/can_handle.lua | 15 + .../sengled/fingerprints.lua | 8 + .../sengled/init.lua | 30 +- .../sub_drivers.lua | 12 + .../zigbee-fan/src/fan-light/init.lua | 6 +- .../zigbee-humidity-sensor/fingerprints.yml | 5 + ...irquality-humidity-temperature-battery.yml | 52 + .../src/aqara/can_handle.lua | 14 + .../src/aqara/fingerprints.lua | 8 + .../zigbee-humidity-sensor/src/aqara/init.lua | 16 +- .../src/centralite-sensor/can_handle.lua | 14 + .../src/centralite-sensor/fingerprints.lua | 10 + .../src/centralite-sensor/init.lua | 31 +- .../src/configurations.lua | 5 +- .../frient-sensor/air-quality/can_handle.lua | 14 + .../air-quality/fingerprints.lua | 8 + .../src/frient-sensor/air-quality/init.lua | 147 + .../src/frient-sensor/can_handle.lua | 14 + .../src/frient-sensor/fingerprints.lua | 10 + .../src/frient-sensor/init.lua | 36 +- .../src/frient-sensor/sub_drivers.lua | 9 + .../src/heiman-sensor/can_handle.lua | 14 + .../src/heiman-sensor/fingerprints.lua | 11 + .../src/heiman-sensor/init.lua | 32 +- .../zigbee-humidity-sensor/src/init.lua | 25 +- .../src/lazy_load_subdriver.lua | 15 + .../src/plaid-systems/can_handle.lua | 15 + .../src/plaid-systems/fingerprints.lua | 10 + .../src/plaid-systems/init.lua | 32 +- .../src/plant-link/can_handle.lua | 15 + .../src/plant-link/fingerprints.lua | 12 + .../src/plant-link/init.lua | 32 +- .../src/sub_drivers.lua | 13 + .../src/test/test_aqara_sensor.lua | 2 +- .../src/test/test_centralite_sensor.lua | 15 +- .../src/test/test_ewelink_sensor.lua | 15 +- .../test/test_frient_air_quality_sensor.lua | 294 ++ .../src/test/test_frient_sensor.lua | 17 +- .../src/test/test_heiman_sensor.lua | 15 +- .../src/test/test_humidity_battery_sensor.lua | 15 +- .../src/test/test_humidity_plaid_systems.lua | 15 +- .../src/test/test_humidity_temperature.lua | 15 +- .../test_humidity_temperature_battery.lua | 15 +- .../test/test_humidity_temperature_sensor.lua | 15 +- .../zigbee-illuminance-sensor/src/init.lua | 8 + .../src/test/test_illuminance_sensor.lua | 2 + .../zigbee-motion-sensor/fingerprints.yml | 5 + .../src/aqara/aqara_utils.lua | 3 + .../src/aqara/can_handle.lua | 14 + .../src/aqara/fingerprints.lua | 10 + .../high-precision-motion/can_handle.lua | 9 + .../src/aqara/high-precision-motion/init.lua | 7 +- .../zigbee-motion-sensor/src/aqara/init.lua | 22 +- .../src/aqara/sub_drivers.lua | 8 + .../src/aurora/can_handle.lua | 11 + .../zigbee-motion-sensor/src/aurora/init.lua | 20 +- .../src/battery-voltage/can_handle.lua | 14 + .../src/battery-voltage/fingerprints.lua | 12 + .../src/battery-voltage/init.lua | 35 +- .../src/centralite/can_handle.lua | 11 + .../src/centralite/init.lua | 20 +- .../src/compacta/can_handle.lua | 11 + .../src/compacta/init.lua | 20 +- .../src/frient/can_handle.lua | 14 + .../src/frient/fingerprints.lua | 10 + .../zigbee-motion-sensor/src/frient/init.lua | 32 +- .../src/gatorsystem/can_handle.lua | 11 + .../src/gatorsystem/init.lua | 20 +- .../src/ikea/can_handle.lua | 14 + .../src/ikea/fingerprints.lua | 8 + .../zigbee-motion-sensor/src/ikea/init.lua | 29 +- .../zigbee-motion-sensor/src/init.lua | 51 +- .../src/iris/can_handle.lua | 14 + .../src/iris/fingerprints.lua | 8 + .../zigbee-motion-sensor/src/iris/init.lua | 29 +- .../src/lazy_load_subdriver.lua | 15 + .../src/motion_timeout/can_handle.lua | 14 + .../src/motion_timeout/fingerprints.lua | 10 + .../src/motion_timeout/init.lua | 34 +- .../src/nyce/can_handle.lua | 14 + .../src/nyce/fingerprints.lua | 10 + .../zigbee-motion-sensor/src/nyce/init.lua | 31 +- .../src/samjin/can_handle.lua | 11 + .../zigbee-motion-sensor/src/samjin/init.lua | 20 +- .../src/sengled/can_handle.lua | 14 + .../src/sengled/fingerprints.lua | 8 + .../zigbee-motion-sensor/src/sengled/init.lua | 16 +- .../src/smartsense/can_handle.lua | 18 + .../src/smartsense/init.lua | 29 +- .../src/smartthings/can_handle.lua | 11 + .../src/smartthings/init.lua | 20 +- .../src/st/zigbee/zdo/init.lua | 20 +- .../test_all_capabilities_zigbee_motion.lua | 16 +- .../src/test/test_aqara_high_precision.lua | 16 +- .../test/test_aqara_motion_illuminance.lua | 16 +- .../src/test/test_aurora_motion.lua | 16 +- .../src/test/test_battery_voltage_motion.lua | 16 +- .../src/test/test_centralite_motion.lua | 16 +- .../src/test/test_compacta_motion.lua | 16 +- .../src/test/test_frient_motion_sensor.lua | 16 +- .../test/test_frient_motion_sensor2_pet.lua | 16 +- .../test/test_frient_motion_sensor_pro.lua | 15 +- .../src/test/test_gator_motion.lua | 16 +- .../src/test/test_ikea_motion.lua | 16 +- .../src/test/test_samjin_sensor.lua | 16 +- .../src/test/test_sengled_motion.lua | 16 +- .../test/test_smartsense_motion_sensor.lua | 16 +- .../src/test/test_smartthings_motion.lua | 16 +- .../src/test/test_thirdreality_sensor.lua | 16 +- .../src/test/test_zigbee_motion_iris.lua | 16 +- .../src/test/test_zigbee_motion_nyce.lua | 16 +- .../src/test/test_zigbee_motion_orvibo.lua | 16 +- .../test/test_zigbee_plugin_motion_sensor.lua | 16 +- .../src/thirdreality/can_handle.lua | 14 + .../src/thirdreality/fingerprints.lua | 9 + .../src/thirdreality/init.lua | 30 +- .../can_handle.lua | 14 + .../fingerprints.lua | 8 + .../src/zigbee-plugin-motion-sensor/init.lua | 29 +- .../zigbee-power-meter/fingerprints.yml | 40 + .../profiles/power-meter-1p.yml | 31 + .../profiles/power-meter-2p.yml | 42 + .../profiles/power-meter-3p.yml | 53 + .../src/bituo/can_handle.lua | 15 + .../src/bituo/fingerprints.lua | 15 + .../zigbee-power-meter/src/bituo/init.lua | 231 ++ .../src/ezex/can_handle.lua | 15 + .../src/ezex/fingerprints.lua | 8 + .../zigbee-power-meter/src/ezex/init.lua | 30 +- .../src/frient/can_handle.lua | 15 + .../src/frient/fingerprints.lua | 9 + .../zigbee-power-meter/src/frient/init.lua | 31 +- .../zigbee-power-meter/src/init.lua | 22 +- .../src/lazy_load_subdriver.lua | 15 + .../src/shinasystems/can_handle.lua | 15 + .../src/shinasystems/fingerprints.lua | 10 + .../src/shinasystems/init.lua | 32 +- .../zigbee-power-meter/src/sub_drivers.lua | 11 + .../src/test/test_zigbee_power_meter_1p.lua | 225 ++ .../src/test/test_zigbee_power_meter_2p.lua | 281 ++ .../src/test/test_zigbee_power_meter_3p.lua | 355 +++ .../src/aqara/can_handle.lua | 13 + .../zigbee-presence-sensor/src/aqara/init.lua | 11 +- .../src/arrival-sensor-v1/can_handle.lua | 12 + .../src/arrival-sensor-v1/init.lua | 22 +- .../zigbee-presence-sensor/src/init.lua | 21 +- .../src/lazy_load_subdriver.lua | 15 + .../src/presence_utils.lua | 15 +- .../src/sub_drivers.lua | 9 + .../src/test/test_st_arrival_sensor_v1.lua | 15 +- .../src/test/test_zigbee_presence_sensor.lua | 15 +- .../zigbee-smoke-detector/fingerprints.yml | 5 + .../profiles/heat-temp-battery-alarm.yml | 55 + .../zigbee-smoke-detector/src/frient/init.lua | 34 +- .../zigbee-smoke-detector/src/init.lua | 3 +- .../src/test/test_frient_heat_detector.lua | 583 ++++ .../src/test/test_frient_smoke_detector.lua | 18 + .../zigbee-switch/fingerprints.yml | 52 + .../zigbee-switch/profiles/aqara-light.yml | 4 - .../profiles/frient-io-output-switch.yml | 25 + .../profiles/inovelli-vzm30-sn.yml | 231 ++ .../profiles/inovelli-vzm32-sn.yml | 355 +++ .../profiles/switch-4inputs-2outputs.yml | 107 + .../src/aqara-light/can_handle.lua | 18 + .../zigbee-switch/src/aqara-light/init.lua | 41 +- .../zigbee-switch/src/aqara/can_handle.lua | 13 + .../zigbee-switch/src/aqara/fingerprints.lua | 22 + .../zigbee-switch/src/aqara/init.lua | 34 +- .../src/aqara/multi-switch/can_handle.lua | 12 + .../src/aqara/multi-switch/fingerprints.lua | 14 + .../src/aqara/multi-switch/init.lua | 27 +- .../zigbee-switch/src/aqara/sub_drivers.lua | 9 + .../src/aqara/version/can_handle.lua | 13 + .../zigbee-switch/src/aqara/version/init.lua | 8 +- .../src/bad_on_off_data_type/can_handle.lua | 24 + .../src/bad_on_off_data_type/init.lua | 39 +- .../src/configurations/devices.lua | 76 +- .../zigbee-switch/src/configurations/init.lua | 15 +- .../zigbee-switch/src/ezex/can_handle.lua | 17 + .../zigbee-switch/src/ezex/init.lua | 32 +- .../src/frient-IO/can_handle.lua | 12 + .../zigbee-switch/src/frient-IO/init.lua | 488 ++++ .../src/frient-IO/unbind_request.lua | 84 + .../zigbee-switch/src/frient/can_handle.lua | 14 + .../zigbee-switch/src/frient/fingerprints.lua | 17 + .../zigbee-switch/src/frient/init.lua | 44 +- .../src/ge-link-bulb/can_handle.lua | 22 + .../zigbee-switch/src/ge-link-bulb/init.lua | 37 +- .../zigbee-switch/src/hanssem/can_handle.lua | 13 + .../src/hanssem/fingerprints.lua | 11 + .../zigbee-switch/src/hanssem/init.lua | 39 +- .../src/ikea-xy-color-bulb/can_handle.lua | 17 + .../src/ikea-xy-color-bulb/init.lua | 34 +- .../SmartThings/zigbee-switch/src/init.lua | 38 +- .../src/inovelli-vzm31-sn/init.lua | 400 --- .../zigbee-switch/src/inovelli/can_handle.lua | 18 + .../zigbee-switch/src/inovelli/common.lua | 73 + .../zigbee-switch/src/inovelli/init.lua | 411 +++ .../src/inovelli/sub_drivers.lua | 9 + .../src/inovelli/vzm30-sn/can_handle.lua | 16 + .../src/inovelli/vzm30-sn/init.lua | 53 + .../src/inovelli/vzm32-sn/can_handle.lua | 16 + .../src/inovelli/vzm32-sn/init.lua | 68 + .../zigbee-switch/src/jasco/can_handle.lua | 18 + .../zigbee-switch/src/jasco/init.lua | 33 +- .../zigbee-switch/src/laisiao/can_handle.lua | 16 + .../zigbee-switch/src/laisiao/init.lua | 29 +- .../zigbee-switch/src/lazy_load_subdriver.lua | 15 + .../src/lifecycle_handlers/device_added.lua | 15 +- .../src/lifecycle_handlers/do_configure.lua | 15 +- .../src/lifecycle_handlers/find_child.lua | 15 +- .../src/lifecycle_handlers/info_changed.lua | 15 +- .../src/multi-switch-no-master/can_handle.lua | 13 + .../multi-switch-no-master/fingerprints.lua | 47 + .../src/multi-switch-no-master/init.lua | 74 +- .../src/non_zigbee_devices/can_handle.lua | 11 + .../src/non_zigbee_devices/init.lua | 26 +- .../zigbee-switch/src/preferences.lua | 33 +- .../zigbee-switch/src/rexense/can_handle.lua | 16 + .../zigbee-switch/src/rexense/init.lua | 31 +- .../zigbee-switch/src/rgb-bulb/can_handle.lua | 23 + .../zigbee-switch/src/rgb-bulb/init.lua | 36 +- .../src/rgbw-bulb/can_handle.lua | 13 + .../src/rgbw-bulb/fingerprints.lua | 74 + .../zigbee-switch/src/rgbw-bulb/init.lua | 99 +- .../zigbee-switch/src/robb/can_handle.lua | 17 + .../zigbee-switch/src/robb/init.lua | 32 +- .../src/sinope-dimmer/can_handle.lua | 12 + .../zigbee-switch/src/sinope-dimmer/init.lua | 25 +- .../zigbee-switch/src/sinope/can_handle.lua | 12 + .../zigbee-switch/src/sinope/init.lua | 25 +- .../zigbee-switch/src/switch_utils.lua | 15 +- .../test/test_all_capability_zigbee_bulb.lua | 15 +- .../src/test/test_aqara_led_bulb.lua | 18 +- .../src/test/test_aqara_light.lua | 18 +- .../src/test/test_aqara_smart_plug.lua | 15 +- .../src/test/test_aqara_smart_plug_t1.lua | 15 +- .../src/test/test_aqara_switch_module.lua | 15 +- .../test_aqara_switch_module_no_power.lua | 15 +- .../src/test/test_aqara_switch_no_power.lua | 15 +- .../src/test/test_aqara_switch_power.lua | 15 +- .../src/test/test_aqara_wall_switch.lua | 15 +- .../src/test/test_aurora_relay.lua | 15 +- .../src/test/test_bad_data_type.lua | 16 +- .../src/test/test_bad_device_kind.lua | 15 +- .../zigbee-switch/src/test/test_cree_bulb.lua | 15 +- .../test/test_duragreen_color_temp_bulb.lua | 15 +- .../test/test_enbrighten_metering_dimmer.lua | 15 +- .../src/test/test_frient_IO_module.lua | 683 +++++ .../src/test/test_frient_switch.lua | 15 +- .../src/test/test_ge_link_bulb.lua | 15 +- .../src/test/test_hanssem_switch.lua | 15 +- .../src/test/test_inovelli-vzm31-sn.lua | 484 ---- .../src/test/test_inovelli_vzm30_sn.lua | 537 ++++ .../src/test/test_inovelli_vzm30_sn_child.lua | 356 +++ .../test_inovelli_vzm30_sn_preferences.lua | 210 ++ .../src/test/test_inovelli_vzm31_sn.lua | 421 +++ .../src/test/test_inovelli_vzm31_sn_child.lua | 346 +++ .../test_inovelli_vzm31_sn_preferences.lua | 200 ++ .../src/test/test_inovelli_vzm32_sn.lua | 527 ++++ .../src/test/test_inovelli_vzm32_sn_child.lua | 345 +++ .../test_inovelli_vzm32_sn_preferences.lua | 163 ++ .../src/test/test_jasco_switch.lua | 15 +- .../src/test/test_laisiao_bath_heather.lua | 15 +- .../src/test/test_multi_switch.lua | 15 +- .../src/test/test_multi_switch_no_master.lua | 18 +- .../src/test/test_multi_switch_power.lua | 15 +- .../src/test/test_on_off_zigbee_bulb.lua | 15 +- .../src/test/test_osram_iqbr30_light.lua | 15 +- .../src/test/test_osram_light.lua | 15 +- .../zigbee-switch/src/test/test_rgb_bulb.lua | 15 +- .../zigbee-switch/src/test/test_rgbw_bulb.lua | 15 +- .../test/test_robb_smarrt_2-wire_dimmer.lua | 15 +- .../src/test/test_robb_smarrt_knob_dimmer.lua | 15 +- .../src/test/test_sengled_color_temp_bulb.lua | 15 +- ...sengled_dimmer_bulb_with_motion_sensor.lua | 15 +- .../src/test/test_sinope_dimmer.lua | 15 +- .../src/test/test_sinope_switch.lua | 15 +- .../src/test/test_switch_power.lua | 15 +- .../src/test/test_tuya_multi.lua | 15 +- .../src/test/test_tuya_multi_switch.lua | 15 +- .../src/test/test_wallhero_switch.lua | 15 +- .../src/test/test_white_color_temp_bulb.lua | 15 +- .../src/test/test_yanmi_switch.lua | 409 +++ .../src/test/test_zigbee_ezex_switch.lua | 15 +- ...metering_plug_power_consumption_report.lua | 84 +- .../test_zigbee_metering_plug_rexense.lua | 15 +- .../src/test/test_zll_color_temp_bulb.lua | 15 +- .../src/test/test_zll_dimmer.lua | 15 +- .../src/test/test_zll_dimmer_bulb.lua | 15 +- .../src/test/test_zll_rgb_bulb.lua | 15 +- .../src/test/test_zll_rgbw_bulb.lua | 15 +- .../src/tuya-multi/can_handle.lua | 21 + .../zigbee-switch/src/tuya-multi/init.lua | 27 +- .../zigbee-switch/src/wallhero/can_handle.lua | 14 + .../src/wallhero/fingerprints.lua | 11 + .../zigbee-switch/src/wallhero/init.lua | 37 +- .../src/white-color-temp-bulb/can_handle.lua | 13 + .../duragreen/can_handle.lua | 18 + .../white-color-temp-bulb/duragreen/init.lua | 29 +- .../white-color-temp-bulb/fingerprints.lua | 99 + .../src/white-color-temp-bulb/init.lua | 128 +- .../src/white-color-temp-bulb/sub_drivers.lua | 7 + .../zigbee-dimmer-power-energy/can_handle.lua | 16 + .../src/zigbee-dimmer-power-energy/init.lua | 31 +- .../src/zigbee-dimming-light/can_handle.lua | 13 + .../src/zigbee-dimming-light/fingerprints.lua | 36 + .../src/zigbee-dimming-light/init.lua | 66 +- .../osram-iqbr30/can_handle.lua | 10 + .../osram-iqbr30/init.lua | 21 +- .../src/zigbee-dimming-light/sub_drivers.lua | 9 + .../zll-dimmer/can_handle.lua | 12 + .../zll-dimmer/fingerprints.lua | 11 + .../zigbee-dimming-light/zll-dimmer/init.lua | 35 +- .../can_handle.lua | 16 + .../src/zigbee-dual-metering-switch/init.lua | 31 +- .../can_handle.lua | 12 + .../init.lua | 26 +- .../aurora-relay/can_handle.lua | 17 + .../zigbee-switch-power/aurora-relay/init.lua | 32 +- .../src/zigbee-switch-power/can_handle.lua | 13 + .../src/zigbee-switch-power/fingerprints.lua | 18 + .../src/zigbee-switch-power/init.lua | 48 +- .../src/zigbee-switch-power/sub_drivers.lua | 9 + .../zigbee-switch-power/vimar/can_handle.lua | 15 + .../src/zigbee-switch-power/vimar/init.lua | 30 +- .../src/zll-dimmer-bulb/can_handle.lua | 13 + .../src/zll-dimmer-bulb/fingerprints.lua | 117 + .../src/zll-dimmer-bulb/init.lua | 142 +- .../src/zll-polling/can_handle.lua | 14 + .../zigbee-switch/src/zll-polling/init.lua | 30 +- .../zigbee-thermostat/fingerprints.yml | 5 + .../src/aqara/can_handle.lua | 14 + .../src/aqara/fingerprints.lua | 8 + .../src/aqara/init.lua | 16 +- .../src/configurations.lua | 16 +- .../src/frient/can_handle.lua | 11 + .../src/frient/init.lua | 20 +- .../zigbee-water-leak-sensor/src/init.lua | 26 +- .../src/lazy_load_subdriver.lua | 15 + .../src/leaksmart/can_handle.lua | 11 + .../src/leaksmart/init.lua | 20 +- .../src/sengled/can_handle.lua | 14 + .../src/sengled/fingerprints.lua | 8 + .../src/sengled/init.lua | 16 +- .../src/sinope/can_handle.lua | 13 + .../src/sinope/init.lua | 29 +- .../src/sub_drivers.lua | 14 + .../src/test/test_aqara_water_leak_sensor.lua | 16 +- .../test_centralite_water_leak_sensor.lua | 16 +- .../test/test_frient_water_leak_sensor.lua | 16 +- .../src/test/test_leaksmart_water.lua | 16 +- .../test/test_samjin_water_leak_sensor.lua | 16 +- .../test/test_sengled_water_leak_sensor.lua | 16 +- .../src/test/test_sinope_zigbee_water.lua | 16 +- .../test_smartthings_water_leak_sensor.lua | 16 +- .../test_thirdreality_water_leak_sensor.lua | 16 +- .../src/test/test_zigbee_water.lua | 16 +- .../src/test/test_zigbee_water_freeze.lua | 16 +- .../src/thirdreality/can_handle.lua | 14 + .../src/thirdreality/fingerprints.lua | 9 + .../src/thirdreality/init.lua | 32 +- .../src/zigbee-water-freeze/can_handle.lua | 11 + .../src/zigbee-water-freeze/init.lua | 20 +- .../zigbee-watering-kit/src/init.lua | 7 +- .../src/lazy_load_subdriver.lua | 15 + .../zigbee-watering-kit/src/sub_drivers.lua | 8 + .../src/thirdreality/can_handle.lua | 11 + .../src/thirdreality/init.lua | 7 +- .../zigbee-window-treatment/fingerprints.yml | 10 + .../profiles/window-shade-only.yml | 12 + .../src/HOPOsmart/custom_clusters.lua | 30 + .../src/HOPOsmart/init.lua | 89 + .../zigbee-window-treatment/src/axis/init.lua | 1 + .../src/feibit/init.lua | 1 + .../zigbee-window-treatment/src/init.lua | 3 +- ...est_zigbee_window_shade_only_HOPOsmart.lua | 198 ++ .../test_zigbee_window_treatment_axis.lua | 2 + .../test_zigbee_window_treatment_feibit.lua | 2 + .../test_zigbee_window_treatment_hanssem.lua | 7 + .../src/aeon-multiwhite-bulb/can_handle.lua | 14 + .../src/aeon-multiwhite-bulb/fingerprints.lua | 10 + .../src/aeon-multiwhite-bulb/init.lua | 31 +- .../src/aeotec-led-bulb-6/can_handle.lua | 23 + .../zwave-bulb/src/aeotec-led-bulb-6/init.lua | 34 +- .../src/fibaro-rgbw-controller/can_handle.lua | 20 + .../src/fibaro-rgbw-controller/init.lua | 31 +- drivers/SmartThings/zwave-bulb/src/init.lua | 22 +- .../zwave-bulb/src/lazy_load_subdriver.lua | 18 + .../zwave-bulb/src/sub_drivers.lua | 10 + .../src/test/test_aeon_multiwhite_bulb.lua | 16 +- .../src/test/test_aeotec_led_bulb_6.lua | 16 +- .../src/test/test_fibaro_rgbw_controller.lua | 16 +- .../zwave-bulb/src/test/test_zwave_bulb.lua | 16 +- .../src/aeon-meter/can_handle.lua | 14 + .../src/aeon-meter/fingerprints.lua | 9 + .../src/aeon-meter/init.lua | 31 +- .../src/aeotec-gen5-meter/can_handle.lua | 14 + .../src/aeotec-gen5-meter/fingerprints.lua | 9 + .../src/aeotec-gen5-meter/init.lua | 31 +- .../zwave-electric-meter/src/init.lua | 22 +- .../src/lazy_load_subdriver.lua | 18 + .../src/qubino-meter/can_handle.lua | 14 + .../src/qubino-meter/fingerprints.lua | 9 + .../src/qubino-meter/init.lua | 30 +- .../zwave-electric-meter/src/sub_drivers.lua | 10 + .../src/test/test_aeon_meter.lua | 16 +- .../src/test/test_aeotec_gen5_meter.lua | 16 +- .../src/test/test_qubino_3_phase_meter.lua | 16 +- .../src/test/test_qubino_smart_meter.lua | 16 +- .../src/test/test_zwave_electric_meter.lua | 16 +- .../SmartThings/zwave-sensor/fingerprints.yml | 6 +- .../profiles/zooz-zse42-water.yml | 52 + .../zwave-sensor/src/configurations.lua | 32 + .../src/firmware-version/init.lua | 91 + drivers/SmartThings/zwave-sensor/src/init.lua | 1 + .../zwave-sensor/src/preferences.lua | 13 + .../src/test/test_firmware_version.lua | 91 + .../src/timed-tamper-clear/init.lua | 39 +- .../zwave-siren/src/aeon-siren/can_handle.lua | 14 + .../zwave-siren/src/aeon-siren/init.lua | 24 +- .../src/aeotec-doorbell-siren/can_handle.lua | 14 + .../aeotec-doorbell-siren/fingerprints.lua | 13 + .../src/aeotec-doorbell-siren/init.lua | 34 +- .../src/apiv6_bugfix/can_handle.lua | 17 + .../zwave-siren/src/apiv6_bugfix/init.lua | 11 +- .../zwave-siren/src/configurations.lua | 16 +- .../src/ecolink-wireless-siren/can_handle.lua | 14 + .../ecolink-wireless-siren/fingerprints.lua | 8 + .../src/ecolink-wireless-siren/init.lua | 29 +- .../zwave-siren/src/fortrezz/can_handle.lua | 13 + .../zwave-siren/src/fortrezz/init.lua | 25 +- drivers/SmartThings/zwave-siren/src/init.lua | 30 +- .../zwave-siren/src/lazy_load_subdriver.lua | 18 + .../src/multifunctional-siren/can_handle.lua | 14 + .../multifunctional-siren/fingerprints.lua | 9 + .../src/multifunctional-siren/init.lua | 30 +- .../src/philio-sound-siren/can_handle.lua | 14 + .../src/philio-sound-siren/fingerprints.lua | 8 + .../src/philio-sound-siren/init.lua | 29 +- .../zwave-siren/src/preferences.lua | 16 +- .../zwave-siren/src/sub_drivers.lua | 18 + .../zwave-siren/src/test/test_aeon_siren.lua | 16 +- .../src/test/test_aeotec_doorbell_siren.lua | 16 +- .../src/test/test_ecolink_wireless_siren.lua | 16 +- .../src/test/test_fortrezz_siren.lua | 18 +- .../src/test/test_philio_sound_siren.lua | 16 +- .../src/test/test_utilitech_siren.lua | 16 +- .../zwave-siren/src/test/test_yale_siren.lua | 16 +- .../src/test/test_zipato_siren.lua | 16 +- .../test/test_zwave_multifunctional-siren.lua | 16 +- .../test/test_zwave_notification_siren.lua | 16 +- .../zwave-siren/src/test/test_zwave_siren.lua | 2 +- .../src/test/test_zwave_sound_sensor.lua | 16 +- .../src/utilitech-siren/can_handle.lua | 13 + .../zwave-siren/src/utilitech-siren/init.lua | 25 +- .../zwave-siren/src/yale-siren/can_handle.lua | 12 + .../zwave-siren/src/yale-siren/init.lua | 24 +- .../src/zipato-siren/can_handle.lua | 12 + .../zwave-siren/src/zipato-siren/init.lua | 22 +- .../src/zwave-sound-sensor/can_handle.lua | 14 + .../src/zwave-sound-sensor/fingerprints.lua | 8 + .../src/zwave-sound-sensor/init.lua | 29 +- .../SmartThings/zwave-switch/fingerprints.yml | 10 +- .../profiles/inovelli-dimmer-power-energy.yml | 24 + .../inovelli-mmwave-dimmer-vzw32-sn.yml | 400 +++ .../zwave-switch/profiles/rgbw-bulb.yml | 16 + .../src/aeon-smart-strip/can_handle.lua | 24 + .../src/aeon-smart-strip/init.lua | 36 +- .../src/aeotec-heavy-duty/can_handle.lua | 18 + .../src/aeotec-heavy-duty/init.lua | 35 +- .../src/aeotec-smart-switch/can_handle.lua | 21 + .../src/aeotec-smart-switch/init.lua | 33 +- .../zwave-switch/src/configurations.lua | 15 +- .../src/dawon-smart-plug/can_handle.lua | 24 + .../src/dawon-smart-plug/init.lua | 35 +- .../dawon-wall-smart-switch/can_handle.lua | 21 + .../dawon-wall-smart-switch/fingerprints.lua | 11 + .../src/dawon-wall-smart-switch/init.lua | 41 +- .../src/eaton-5-scene-keypad/can_handle.lua | 19 + .../src/eaton-5-scene-keypad/init.lua | 31 +- .../src/eaton-accessory-dimmer/can_handle.lua | 18 + .../src/eaton-accessory-dimmer/init.lua | 31 +- .../src/eaton-anyplace-switch/can_handle.lua | 19 + .../src/eaton-anyplace-switch/init.lua | 31 +- .../src/ecolink-switch/can_handle.lua | 16 + .../src/ecolink-switch/fingerprints.lua | 10 + .../zwave-switch/src/ecolink-switch/init.lua | 35 +- .../src/fibaro-double-switch/can_handle.lua | 21 + .../src/fibaro-double-switch/init.lua | 33 +- .../src/fibaro-single-switch/can_handle.lua | 20 + .../src/fibaro-single-switch/init.lua | 33 +- .../src/fibaro-wall-plug-us/can_handle.lua | 19 + .../src/fibaro-wall-plug-us/init.lua | 32 +- drivers/SmartThings/zwave-switch/src/init.lua | 32 +- .../can_handle.lua | 16 + .../fingerprints.lua | 13 + .../inovelli-2-channel-smart-plug/init.lua | 38 +- .../zwave-switch/src/inovelli-LED/init.lua | 111 - .../inovelli-LED/inovelli-lzw31sn/init.lua | 112 - .../zwave-switch/src/inovelli/can_handle.lua | 20 + .../zwave-switch/src/inovelli/init.lua | 501 ++++ .../src/inovelli/lzw31-sn/can_handle.lua | 19 + .../src/inovelli/lzw31-sn/init.lua | 74 + .../zwave-switch/src/inovelli/sub_drivers.lua | 9 + .../src/inovelli/vzw32-sn/can_handle.lua | 19 + .../src/inovelli/vzw32-sn/init.lua | 73 + .../zwave-switch/src/lazy_load_subdriver.lua | 18 + .../src/multi-metering-switch/can_handle.lua | 16 + .../multi-metering-switch/fingerprints.lua | 18 + .../src/multi-metering-switch/init.lua | 43 +- .../multi_metering_switch_configurations.lua | 15 +- .../src/multichannel-device/can_handle.lua | 14 + .../src/multichannel-device/init.lua | 28 +- .../zwave-switch/src/preferences.lua | 55 +- .../src/qubino-switches/can_handle.lua | 14 + .../constants/qubino-constants.lua | 15 +- .../src/qubino-switches/fingerprints.lua | 12 + .../zwave-switch/src/qubino-switches/init.lua | 42 +- .../qubino-dimmer/can_handle.lua | 15 + .../qubino-dimmer/fingerprints.lua | 9 + .../qubino-switches/qubino-dimmer/init.lua | 37 +- .../qubino-din-dimmer/can_handle.lua | 12 + .../qubino-dimmer/qubino-din-dimmer/init.lua | 25 +- .../qubino-dimmer/sub_drivers.lua | 8 + .../qubino-relays/can_handle.lua | 19 + .../qubino-switches/qubino-relays/init.lua | 38 +- .../qubino-flush-1-relay/can_handle.lua | 14 + .../qubino-flush-1-relay/init.lua | 23 +- .../qubino-flush-1d-relay/can_handle.lua | 14 + .../qubino-flush-1d-relay/init.lua | 23 +- .../qubino-flush-2-relay/can_handle.lua | 13 + .../qubino-flush-2-relay/init.lua | 23 +- .../qubino-relays/sub_drivers.lua | 9 + .../src/qubino-switches/sub_drivers.lua | 9 + .../zwave-switch/src/switch_utils.lua | 17 +- .../src/test/test_aeon_smart_strip.lua | 15 +- .../src/test/test_aeotec_dimmer_switch.lua | 15 +- ..._aeotec_dual_nano_switch_configuration.lua | 15 +- .../test/test_aeotec_heavy_duty_switch.lua | 17 +- ...t_aeotec_metering_switch_configuration.lua | 15 +- .../src/test/test_aeotec_nano_dimmer.lua | 15 +- .../test_aeotec_nano_dimmer_preferences.lua | 15 +- .../src/test/test_aeotec_smart_switch.lua | 15 +- .../test/test_aeotec_smart_switch_7_eu.lua | 15 +- .../test/test_aeotec_smart_switch_7_us.lua | 15 +- .../test/test_aeotec_smart_switch_gen5.lua | 15 +- .../src/test/test_dawon_smart_plug.lua | 15 +- .../src/test/test_dawon_wall_smart_switch.lua | 15 +- .../src/test/test_eaton_5_scene_keypad.lua | 15 +- .../src/test/test_eaton_accessory_dimmer.lua | 15 +- .../src/test/test_eaton_anyplace_switch.lua | 15 +- .../src/test/test_eaton_rf_dimmer.lua | 15 +- .../src/test/test_ecolink_switch.lua | 15 +- .../src/test/test_fibaro_double_switch.lua | 15 +- .../src/test/test_fibaro_single_switch.lua | 15 +- .../src/test/test_fibaro_wall_plug_eu.lua | 15 +- ...test_fibaro_wall_plug_uk_configuration.lua | 15 +- .../src/test/test_fibaro_wall_plug_us.lua | 15 +- .../test_fibaro_walli_dimmer_preferences.lua | 15 +- .../test/test_fibaro_walli_double_switch.lua | 15 +- ...fibaro_walli_double_switch_preferences.lua | 15 +- .../src/test/test_generic_zwave_device1.lua | 15 +- ...go_control_plug_in_switch_configuraton.lua | 15 +- .../src/test/test_honeywell_dimmer.lua | 15 +- .../test_inovelli_2_channel_smart_plug.lua | 15 +- .../src/test/test_inovelli_button.lua | 64 +- .../src/test/test_inovelli_dimmer.lua | 15 +- .../src/test/test_inovelli_dimmer_led.lua | 15 +- .../test_inovelli_dimmer_power_energy.lua | 15 +- .../test/test_inovelli_dimmer_preferences.lua | 15 +- .../src/test/test_inovelli_dimmer_scenes.lua | 35 +- .../src/test/test_inovelli_vzw32_sn.lua | 325 +++ .../src/test/test_inovelli_vzw32_sn_child.lua | 335 +++ .../test_inovelli_vzw32_sn_preferences.lua | 151 + .../src/test/test_multi_metering_switch.lua | 15 +- .../src/test/test_multichannel_device.lua | 17 +- .../test_popp_outdoor_plug_configuration.lua | 15 +- .../src/test/test_qubino_din_dimmer.lua | 15 +- .../test_qubino_din_dimmer_preferences.lua | 15 +- .../test_qubino_flush_1_relay_preferences.lua | 15 +- ...test_qubino_flush_1d_relay_preferences.lua | 15 +- .../src/test/test_qubino_flush_2_relay.lua | 15 +- .../test_qubino_flush_2_relay_preferences.lua | 15 +- .../src/test/test_qubino_flush_dimmer.lua | 15 +- ..._qubino_flush_dimmer_0_10V_preferences.lua | 15 +- .../test_qubino_flush_dimmer_preferences.lua | 15 +- .../test_qubino_mini_dimmer_preferences.lua | 15 +- ...t_qubino_temperature_sensor_with_power.lua | 15 +- ...ubino_temperature_sensor_without_power.lua | 15 +- .../test_shelly_multi_metering_switch.lua | 15 +- .../zwave-switch/src/test/test_wyfy_touch.lua | 15 +- .../test/test_wyfy_touch_configuration.lua | 15 +- .../src/test/test_zooz_double_plug.lua | 15 +- .../src/test/test_zooz_power_strip.lua | 15 +- .../test/test_zooz_zen_30_dimmer_relay.lua | 15 +- ...t_zooz_zen_30_dimmer_relay_preferences.lua | 15 +- .../test/test_zwave_dimmer_power_energy.lua | 15 +- .../src/test/test_zwave_dual_switch.lua | 15 +- .../test/test_zwave_dual_switch_migration.lua | 15 +- .../src/test/test_zwave_switch.lua | 15 +- .../src/test/test_zwave_switch_battery.lua | 15 +- .../test/test_zwave_switch_electric_meter.lua | 15 +- .../test/test_zwave_switch_energy_meter.lua | 15 +- .../src/test/test_zwave_switch_level.lua | 15 +- .../test/test_zwave_switch_power_meter.lua | 15 +- .../src/zooz-power-strip/can_handle.lua | 18 + .../src/zooz-power-strip/init.lua | 31 +- .../zooz-zen-30-dimmer-relay/can_handle.lua | 17 + .../src/zooz-zen-30-dimmer-relay/init.lua | 31 +- .../src/zwave-dual-switch/can_handle.lua | 16 + .../dual_switch_configurations.lua | 15 +- .../src/zwave-dual-switch/fingerprints.lua | 14 + .../src/zwave-dual-switch/init.lua | 39 +- .../aeotec-radiator-thermostat/can_handle.lua | 14 + .../src/aeotec-radiator-thermostat/init.lua | 24 +- .../src/apiv6_bugfix/can_handle.lua | 25 + .../src/apiv6_bugfix/fingerprints.lua | 9 + .../src/apiv6_bugfix/init.lua | 22 +- .../src/ct100-thermostat/can_handle.lua | 15 + .../src/ct100-thermostat/fingerprints.lua | 9 + .../src/ct100-thermostat/init.lua | 33 +- .../src/fibaro-heat-controller/can_handle.lua | 15 + .../fibaro-heat-controller/fingerprints.lua | 9 + .../src/fibaro-heat-controller/init.lua | 31 +- .../SmartThings/zwave-thermostat/src/init.lua | 27 +- .../src/lazy_load_subdriver.lua | 18 + .../popp-radiator-thermostat/can_handle.lua | 14 + .../src/popp-radiator-thermostat/init.lua | 24 +- .../qubino-flush-thermostat/can_handle.lua | 14 + .../qubino-flush-thermostat/fingerprints.lua | 8 + .../src/qubino-flush-thermostat/init.lua | 29 +- .../src/stelpro-ki-thermostat/can_handle.lua | 15 + .../stelpro-ki-thermostat/fingerprints.lua | 8 + .../src/stelpro-ki-thermostat/init.lua | 30 +- .../zwave-thermostat/src/sub_drivers.lua | 15 + .../thermostat-heating-battery/can_handle.lua | 15 + .../fingerprints.lua | 9 + .../src/thermostat-heating-battery/init.lua | 33 +- .../src/aeotec-nano-shutter/can_handle.lua | 14 + .../src/aeotec-nano-shutter/fingerprints.lua | 11 + .../src/aeotec-nano-shutter/init.lua | 32 +- .../iblinds-window-treatment/can_handle.lua | 14 + .../iblinds-window-treatment/fingerprints.lua | 10 + .../src/iblinds-window-treatment/init.lua | 35 +- .../iblinds-window-treatment/sub_drivers.lua | 8 + .../v3/can_handle.lua | 19 + .../v3/fingerprints.lua | 9 + .../{v3.lua => v3/init.lua} | 16 +- .../zwave-window-treatment/src/init.lua | 23 +- .../src/lazy_load_subdriver.lua | 18 + .../src/preferences.lua | 16 +- .../can_handle.lua | 14 + .../fingerprints.lua | 9 + .../src/springs-window-fashion-shade/init.lua | 30 +- .../src/sub_drivers.lua | 11 + .../src/test/test_fibaro_roller_shutter.lua | 16 +- .../src/test/test_qubino_flush_shutter.lua | 16 +- .../test/test_zwave_aeotec_nano_shutter.lua | 16 +- .../test_zwave_iblinds_window_treatment.lua | 16 +- .../test_zwave_springs_window_treatment.lua | 16 +- .../src/test/test_zwave_window_treatment.lua | 16 +- .../window-treatment-venetian/can_handle.lua | 14 + .../fibaro-roller-shutter/can_handle.lua | 14 + .../fibaro-roller-shutter/fingerprints.lua | 8 + .../fibaro-roller-shutter/init.lua | 29 +- .../fingerprints.lua | 10 + .../src/window-treatment-venetian/init.lua | 37 +- .../qubino-flush-shutter/can_handle.lua | 14 + .../qubino-flush-shutter/fingerprints.lua | 9 + .../qubino-flush-shutter/init.lua | 30 +- .../window-treatment-venetian/sub_drivers.lua | 9 + .../src/window_preset_defaults.lua | 18 +- .../src/test/test_tuya_curtain.lua | 10 +- .../tuya-zigbee/src/test/test_tuya_switch.lua | 22 +- tools/localizations/cn.csv | 14 + tools/pre-commit | 45 + tools/run_driver_tests.py | 34 +- 1238 files changed, 34567 insertions(+), 18075 deletions(-) create mode 100644 drivers/SmartThings/matter-appliance/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/matter-appliance/src/matter-cook-top/can_handle.lua create mode 100644 drivers/SmartThings/matter-appliance/src/matter-dishwasher/can_handle.lua create mode 100644 drivers/SmartThings/matter-appliance/src/matter-extractor-hood/can_handle.lua create mode 100644 drivers/SmartThings/matter-appliance/src/matter-laundry/can_handle.lua create mode 100644 drivers/SmartThings/matter-appliance/src/matter-microwave-oven/can_handle.lua create mode 100644 drivers/SmartThings/matter-appliance/src/matter-oven/can_handle.lua create mode 100644 drivers/SmartThings/matter-appliance/src/matter-refrigerator/can_handle.lua create mode 100644 drivers/SmartThings/matter-appliance/src/sub_drivers.lua create mode 100644 drivers/SmartThings/matter-sensor/profiles/smoke-temp-humidity-battery.yml delete mode 100644 drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/init.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-sensor/src/air-quality-sensor/init.lua rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/AirQuality/init.lua (86%) rename drivers/SmartThings/{matter-thermostat/src/WaterHeaterMode => matter-sensor/src/embedded_clusters/AirQuality}/server/attributes/AcceptedCommandList.lua (94%) rename drivers/SmartThings/{matter-thermostat/src => matter-sensor/src/embedded_clusters}/AirQuality/server/attributes/AirQuality.lua (88%) rename drivers/SmartThings/{matter-thermostat/src => matter-sensor/src/embedded_clusters}/AirQuality/server/attributes/AttributeList.lua (94%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/AirQuality/server/attributes/EventList.lua (94%) rename drivers/SmartThings/{matter-thermostat/src => matter-sensor/src/embedded_clusters}/AirQuality/server/attributes/init.lua (75%) rename drivers/SmartThings/{matter-thermostat/src => matter-sensor/src/embedded_clusters}/AirQuality/types/AirQualityEnum.lua (94%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/AirQuality/types/Feature.lua (96%) rename drivers/SmartThings/{matter-thermostat/src => matter-sensor/src/embedded_clusters}/AirQuality/types/init.lua (60%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/BooleanStateConfiguration/init.lua (92%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/BooleanStateConfiguration/server/attributes/CurrentSensitivityLevel.lua (95%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/BooleanStateConfiguration/server/attributes/DefaultSensitivityLevel.lua (94%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/BooleanStateConfiguration/server/attributes/SensorFault.lua (87%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/BooleanStateConfiguration/server/attributes/SupportedSensitivityLevels.lua (94%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/BooleanStateConfiguration/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/BooleanStateConfiguration/types/Feature.lua (97%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/BooleanStateConfiguration/types/SensorFaultBitmap.lua (93%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/BooleanStateConfiguration/types/init.lua (62%) rename drivers/SmartThings/{matter-thermostat/src => matter-sensor/src/embedded_clusters}/CarbonDioxideConcentrationMeasurement/init.lua (88%) rename drivers/SmartThings/matter-sensor/src/{NitrogenDioxideConcentrationMeasurement => embedded_clusters/CarbonDioxideConcentrationMeasurement}/server/attributes/LevelValue.lua (81%) rename drivers/SmartThings/matter-sensor/src/{CarbonMonoxideConcentrationMeasurement => embedded_clusters/CarbonDioxideConcentrationMeasurement}/server/attributes/MeasuredValue.lua (89%) rename drivers/SmartThings/matter-sensor/src/{CarbonMonoxideConcentrationMeasurement => embedded_clusters/CarbonDioxideConcentrationMeasurement}/server/attributes/MeasurementUnit.lua (82%) rename drivers/SmartThings/{matter-thermostat/src => matter-sensor/src/embedded_clusters}/CarbonDioxideConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/{matter-thermostat/src => matter-sensor/src/embedded_clusters}/CarbonMonoxideConcentrationMeasurement/init.lua (88%) rename drivers/SmartThings/matter-sensor/src/{CarbonDioxideConcentrationMeasurement => embedded_clusters/CarbonMonoxideConcentrationMeasurement}/server/attributes/LevelValue.lua (81%) rename drivers/SmartThings/matter-sensor/src/{CarbonDioxideConcentrationMeasurement => embedded_clusters/CarbonMonoxideConcentrationMeasurement}/server/attributes/MeasuredValue.lua (89%) rename drivers/SmartThings/matter-sensor/src/{NitrogenDioxideConcentrationMeasurement => embedded_clusters/CarbonMonoxideConcentrationMeasurement}/server/attributes/MeasurementUnit.lua (82%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/CarbonMonoxideConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/ConcentrationMeasurement/init.lua (92%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/ConcentrationMeasurement/server/attributes/LevelValue.lua (88%) rename drivers/SmartThings/{matter-thermostat/src => matter-sensor/src/embedded_clusters}/ConcentrationMeasurement/server/attributes/MeasuredValue.lua (93%) rename drivers/SmartThings/{matter-thermostat/src => matter-sensor/src/embedded_clusters}/ConcentrationMeasurement/server/attributes/MeasurementUnit.lua (88%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/ConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/ConcentrationMeasurement/types/Feature.lua (98%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/ConcentrationMeasurement/types/LevelValueEnum.lua (93%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/ConcentrationMeasurement/types/MeasurementUnitEnum.lua (94%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/ConcentrationMeasurement/types/init.lua (62%) rename drivers/SmartThings/{matter-thermostat/src => matter-sensor/src/embedded_clusters}/FormaldehydeConcentrationMeasurement/init.lua (88%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua (81%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua (89%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua (82%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/FormaldehydeConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/NitrogenDioxideConcentrationMeasurement/init.lua (88%) rename drivers/SmartThings/matter-sensor/src/{CarbonMonoxideConcentrationMeasurement => embedded_clusters/NitrogenDioxideConcentrationMeasurement}/server/attributes/LevelValue.lua (81%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua (89%) rename drivers/SmartThings/matter-sensor/src/{CarbonDioxideConcentrationMeasurement => embedded_clusters/NitrogenDioxideConcentrationMeasurement}/server/attributes/MeasurementUnit.lua (82%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/NitrogenDioxideConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/OzoneConcentrationMeasurement/init.lua (85%) create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/OzoneConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/{matter-thermostat/src => matter-sensor/src/embedded_clusters}/Pm10ConcentrationMeasurement/init.lua (85%) create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/Pm10ConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/{matter-thermostat/src => matter-sensor/src/embedded_clusters}/Pm1ConcentrationMeasurement/init.lua (85%) create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/Pm1ConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/Pm25ConcentrationMeasurement/init.lua (85%) create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/Pm25ConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/init.lua (82%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/attributes/AcceptedCommandList.lua (85%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/attributes/AttributeList.lua (84%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/attributes/EventList.lua (84%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/attributes/MaxMeasuredValue.lua (83%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/attributes/MaxScaledValue.lua (83%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/attributes/MeasuredValue.lua (82%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/attributes/MinMeasuredValue.lua (83%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/attributes/MinScaledValue.lua (83%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/attributes/Scale.lua (82%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/attributes/ScaledTolerance.lua (83%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/attributes/ScaledValue.lua (82%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/attributes/Tolerance.lua (82%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/attributes/init.lua (72%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/commands/init.lua (53%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/server/events/init.lua (51%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/PressureMeasurement/types/Feature.lua (73%) create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/types/init.lua rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/RadonConcentrationMeasurement/init.lua (85%) create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/RadonConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/SmokeCoAlarm/init.lua (90%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/SmokeCoAlarm/server/attributes/BatteryAlert.lua (89%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/SmokeCoAlarm/server/attributes/COState.lua (88%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/SmokeCoAlarm/server/attributes/HardwareFaultAlert.lua (94%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/SmokeCoAlarm/server/attributes/SmokeSensitivityLevel.lua (91%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/SmokeCoAlarm/server/attributes/SmokeState.lua (88%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/SmokeCoAlarm/server/attributes/TestInProgress.lua (93%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/SmokeCoAlarm/server/attributes/init.lua (75%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/SmokeCoAlarm/server/commands/SelfTestRequest.lua (96%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/SmokeCoAlarm/server/commands/init.lua (76%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/SmokeCoAlarm/types/AlarmStateEnum.lua (92%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/SmokeCoAlarm/types/Feature.lua (95%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/SmokeCoAlarm/types/SensitivityEnum.lua (91%) rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/SmokeCoAlarm/types/init.lua (60%) rename drivers/SmartThings/{matter-thermostat/src => matter-sensor/src/embedded_clusters}/TotalVolatileOrganicCompoundsConcentrationMeasurement/init.lua (89%) create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-sensor/src/{ => embedded_clusters}/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/init.lua (76%) create mode 100644 drivers/SmartThings/matter-sensor/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua create mode 100644 drivers/SmartThings/matter-sensor/src/sensor_utils/device_configuration.lua rename drivers/SmartThings/matter-sensor/src/{embedded-cluster-utils.lua => sensor_utils/embedded_cluster_utils.lua} (70%) create mode 100644 drivers/SmartThings/matter-sensor/src/sensor_utils/fields.lua create mode 100644 drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers.lua create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_handlers/attribute_handlers.lua create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/device_configuration.lua create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/fields.lua create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/can_handle.lua create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/bosch_button_contact/can_handle.lua rename drivers/SmartThings/matter-sensor/src/{bosch-button-contact => sub_drivers/bosch_button_contact}/init.lua (85%) create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/smoke_co_alarm/can_handle.lua rename drivers/SmartThings/matter-sensor/src/{smoke-co-alarm => sub_drivers/smoke_co_alarm}/init.lua (61%) create mode 100644 drivers/SmartThings/matter-switch/profiles/3-button-motion.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/6-button-motion.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/camera.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/fan-modular.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/light-energy-powerConsumption.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/light-level-energy-powerConsumption.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/light-level-power.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/light-power.yml create mode 100644 drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/server/attributes/PartsList.lua create mode 100644 drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/server/attributes/AvailableEndpoints.lua create mode 100644 drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/types/Feature.lua create mode 100644 drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/types/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/aqara_cube/can_handle.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/attribute_handlers.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/capability_handlers.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/event_handlers.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/device_configuration.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/fields.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/utils.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/camera/can_handle.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/eve_energy/can_handle.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/can_handle.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/device_configuration.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/fields.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/utils.lua create mode 100644 drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_mk1/can_handle.lua rename drivers/SmartThings/matter-switch/src/{generic_handlers => switch_handlers}/attribute_handlers.lua (73%) rename drivers/SmartThings/matter-switch/src/{generic_handlers => switch_handlers}/capability_handlers.lua (82%) rename drivers/SmartThings/matter-switch/src/{generic_handlers => switch_handlers}/event_handlers.lua (85%) rename drivers/SmartThings/matter-switch/src/{utils => switch_utils}/color_utils.lua (77%) create mode 100644 drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua rename drivers/SmartThings/matter-switch/src/{utils => switch_utils}/embedded_cluster_utils.lua (79%) create mode 100644 drivers/SmartThings/matter-switch/src/switch_utils/fields.lua create mode 100644 drivers/SmartThings/matter-switch/src/switch_utils/utils.lua create mode 100644 drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua rename drivers/SmartThings/matter-switch/src/test/{test_electrical_sensor.lua => test_electrical_sensor_tree.lua} (63%) create mode 100644 drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua create mode 100644 drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua create mode 100644 drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_motion.lua delete mode 100644 drivers/SmartThings/matter-switch/src/utils/device_configuration.lua delete mode 100644 drivers/SmartThings/matter-switch/src/utils/switch_fields.lua delete mode 100644 drivers/SmartThings/matter-switch/src/utils/switch_utils.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/AcceptedCommandList.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyExported.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyExported.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/CumulativeEnergyMeasured.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/PeriodicEnergyMeasured.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/init.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/Feature.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/init.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/types/Feature.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/types/init.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/AttributeList.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua delete mode 100644 drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ActivatedCarbonFilterMonitoring/init.lua (90%) rename drivers/SmartThings/matter-thermostat/src/{HepaFilterMonitoring => embedded_clusters/ActivatedCarbonFilterMonitoring}/server/attributes/ChangeIndication.lua (88%) rename drivers/SmartThings/matter-thermostat/src/{HepaFilterMonitoring => embedded_clusters/ActivatedCarbonFilterMonitoring}/server/attributes/Condition.lua (89%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ActivatedCarbonFilterMonitoring/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ActivatedCarbonFilterMonitoring/server/commands/ResetCondition.lua (96%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ActivatedCarbonFilterMonitoring/server/commands/init.lua (76%) rename drivers/SmartThings/matter-thermostat/src/{HepaFilterMonitoring => embedded_clusters/ActivatedCarbonFilterMonitoring}/types/ChangeIndicationEnum.lua (92%) rename drivers/SmartThings/matter-thermostat/src/{HepaFilterMonitoring => embedded_clusters/ActivatedCarbonFilterMonitoring}/types/Feature.lua (96%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ActivatedCarbonFilterMonitoring/types/init.lua (62%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/AirQuality/init.lua (86%) rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/AirQuality/server/attributes/AcceptedCommandList.lua (94%) rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/AirQuality/server/attributes/AirQuality.lua (88%) rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/AirQuality/server/attributes/AttributeList.lua (94%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/AirQuality/server/attributes/EventList.lua (94%) rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/AirQuality/server/attributes/init.lua (75%) rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/AirQuality/types/AirQualityEnum.lua (94%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/AirQuality/types/Feature.lua (96%) rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/AirQuality/types/init.lua (60%) rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/CarbonDioxideConcentrationMeasurement/init.lua (88%) create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/CarbonDioxideConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/CarbonMonoxideConcentrationMeasurement/init.lua (88%) create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/CarbonMonoxideConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ConcentrationMeasurement/init.lua (92%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ConcentrationMeasurement/server/attributes/LevelValue.lua (88%) rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/ConcentrationMeasurement/server/attributes/MeasuredValue.lua (93%) rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/ConcentrationMeasurement/server/attributes/MeasurementUnit.lua (88%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ConcentrationMeasurement/types/Feature.lua (98%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ConcentrationMeasurement/types/LevelValueEnum.lua (93%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ConcentrationMeasurement/types/MeasurementUnitEnum.lua (94%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ConcentrationMeasurement/types/init.lua (62%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ElectricalEnergyMeasurement/init.lua (61%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua (88%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua (88%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ElectricalEnergyMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua (97%) create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/types/Feature.lua rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ElectricalEnergyMeasurement/types/init.lua (62%) create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalPowerMeasurement/init.lua rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ElectricalPowerMeasurement/server/attributes/ActivePower.lua (93%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/ElectricalPowerMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/FormaldehydeConcentrationMeasurement/init.lua (88%) create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/FormaldehydeConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/HepaFilterMonitoring/init.lua (89%) rename drivers/SmartThings/matter-thermostat/src/{ActivatedCarbonFilterMonitoring => embedded_clusters/HepaFilterMonitoring}/server/attributes/ChangeIndication.lua (88%) rename drivers/SmartThings/matter-thermostat/src/{ActivatedCarbonFilterMonitoring => embedded_clusters/HepaFilterMonitoring}/server/attributes/Condition.lua (93%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/HepaFilterMonitoring/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/HepaFilterMonitoring/server/commands/ResetCondition.lua (96%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/HepaFilterMonitoring/server/commands/init.lua (76%) rename drivers/SmartThings/matter-thermostat/src/{ActivatedCarbonFilterMonitoring => embedded_clusters/HepaFilterMonitoring}/types/ChangeIndicationEnum.lua (92%) rename drivers/SmartThings/matter-thermostat/src/{ActivatedCarbonFilterMonitoring => embedded_clusters/HepaFilterMonitoring}/types/Feature.lua (96%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/HepaFilterMonitoring/types/init.lua (61%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/NitrogenDioxideConcentrationMeasurement/init.lua (88%) create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/NitrogenDioxideConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/OzoneConcentrationMeasurement/init.lua (85%) create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/OzoneConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/Pm10ConcentrationMeasurement/init.lua (85%) create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/Pm10ConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/Pm1ConcentrationMeasurement/init.lua (85%) create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/Pm1ConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/Pm25ConcentrationMeasurement/init.lua (85%) create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/Pm25ConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/RadonConcentrationMeasurement/init.lua (85%) create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/RadonConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/{matter-sensor/src => matter-thermostat/src/embedded_clusters}/TotalVolatileOrganicCompoundsConcentrationMeasurement/init.lua (89%) create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/init.lua (76%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/WaterHeaterMode/init.lua (87%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/WaterHeaterMode/server/attributes/CurrentMode.lua (93%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/WaterHeaterMode/server/attributes/SupportedModes.lua (90%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/WaterHeaterMode/server/attributes/init.lua (75%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/WaterHeaterMode/server/commands/ChangeToMode.lua (96%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/WaterHeaterMode/server/commands/init.lua (76%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/WaterHeaterMode/types/Feature.lua (92%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/WaterHeaterMode/types/ModeOptionStruct.lua (94%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/WaterHeaterMode/types/ModeTag.lua (90%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/WaterHeaterMode/types/ModeTagStruct.lua (96%) rename drivers/SmartThings/matter-thermostat/src/{ => embedded_clusters}/WaterHeaterMode/types/init.lua (61%) create mode 100644 drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/thermostat_handlers/capability_handlers.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/thermostat_utils/device_configuration.lua rename drivers/SmartThings/matter-thermostat/src/{embedded-cluster-utils.lua => thermostat_utils/embedded_cluster_utils.lua} (67%) create mode 100644 drivers/SmartThings/matter-thermostat/src/thermostat_utils/fields.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/thermostat_utils/legacy_device_configuration.lua create mode 100644 drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua create mode 100644 drivers/SmartThings/zigbee-button/profiles/aqara-double-buttons-mode.yml create mode 100644 drivers/SmartThings/zigbee-button/profiles/aqara-double-buttons.yml create mode 100644 drivers/SmartThings/zigbee-button/profiles/aqara-single-button-mode.yml create mode 100644 drivers/SmartThings/zigbee-button/profiles/one-button-batteryLevel.yml create mode 100644 drivers/SmartThings/zigbee-carbon-monoxide-detector/src/ClimaxTechnology/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-carbon-monoxide-detector/src/ClimaxTechnology/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-carbon-monoxide-detector/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-carbon-monoxide-detector/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-dimmer-remote/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-dimmer-remote/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-accessory-dimmer/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-accessory-dimmer/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/IKEAofSweden/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/IKEAofSweden/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sengled/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sengled/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/profiles/frient-airquality-humidity-temperature-battery.yml create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/aqara/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/aqara/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/centralite-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/centralite-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/air-quality/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/air-quality/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/air-quality/init.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/heiman-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/heiman-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/plaid-systems/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/plaid-systems/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/plant-link/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/plant-link/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_air_quality_sensor.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/aqara/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/aqara/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/aqara/high-precision-motion/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/aqara/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/aurora/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/battery-voltage/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/battery-voltage/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/centralite/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/compacta/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/frient/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/frient/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/gatorsystem/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/ikea/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/ikea/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/iris/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/iris/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/motion_timeout/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/motion_timeout/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/nyce/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/nyce/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/samjin/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/sengled/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/sengled/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/smartsense/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/smartthings/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/thirdreality/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/thirdreality/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/zigbee-plugin-motion-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-motion-sensor/src/zigbee-plugin-motion-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/profiles/power-meter-1p.yml create mode 100644 drivers/SmartThings/zigbee-power-meter/profiles/power-meter-2p.yml create mode 100644 drivers/SmartThings/zigbee-power-meter/profiles/power-meter-3p.yml create mode 100644 drivers/SmartThings/zigbee-power-meter/src/bituo/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/bituo/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/bituo/init.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/ezex/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/ezex/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/frient/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/frient/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/shinasystems/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/shinasystems/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_2p.lua create mode 100644 drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_3p.lua create mode 100644 drivers/SmartThings/zigbee-presence-sensor/src/aqara/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-presence-sensor/src/arrival-sensor-v1/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-presence-sensor/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-presence-sensor/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/profiles/heat-temp-battery-alarm.yml create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_heat_detector.lua create mode 100644 drivers/SmartThings/zigbee-switch/profiles/frient-io-output-switch.yml create mode 100644 drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm30-sn.yml create mode 100644 drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml create mode 100644 drivers/SmartThings/zigbee-switch/profiles/switch-4inputs-2outputs.yml create mode 100644 drivers/SmartThings/zigbee-switch/src/aqara-light/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/aqara/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/aqara/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/aqara/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/aqara/version/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/bad_on_off_data_type/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/ezex/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/frient-IO/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/frient-IO/init.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/frient-IO/unbind_request.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/frient/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/frient/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/ge-link-bulb/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/hanssem/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/hanssem/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/ikea-xy-color-bulb/can_handle.lua delete mode 100644 drivers/SmartThings/zigbee-switch/src/inovelli-vzm31-sn/init.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/inovelli/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/inovelli/common.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/inovelli/init.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/inovelli/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/inovelli/vzm30-sn/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/inovelli/vzm30-sn/init.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/inovelli/vzm32-sn/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/inovelli/vzm32-sn/init.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/jasco/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/laisiao/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/multi-switch-no-master/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/multi-switch-no-master/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/non_zigbee_devices/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/rexense/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/rgb-bulb/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/rgbw-bulb/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/rgbw-bulb/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/robb/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/sinope-dimmer/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/sinope/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_frient_IO_module.lua delete mode 100755 drivers/SmartThings/zigbee-switch/src/test/test_inovelli-vzm31-sn.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn_child.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn_preferences.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn_child.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn_preferences.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn_child.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn_preferences.lua create mode 100755 drivers/SmartThings/zigbee-switch/src/test/test_yanmi_switch.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/tuya-multi/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/wallhero/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/wallhero/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/duragreen/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-dimmer-power-energy/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/osram-iqbr30/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/zll-dimmer/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/zll-dimmer/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-dual-metering-switch/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-metering-plug-power-consumption-report/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/aurora-relay/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/vimar/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zll-dimmer-bulb/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zll-dimmer-bulb/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-switch/src/zll-polling/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-water-leak-sensor/src/aqara/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-water-leak-sensor/src/aqara/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-water-leak-sensor/src/frient/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-water-leak-sensor/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-water-leak-sensor/src/leaksmart/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-water-leak-sensor/src/sengled/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-water-leak-sensor/src/sengled/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-water-leak-sensor/src/sinope/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-water-leak-sensor/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-water-leak-sensor/src/thirdreality/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-water-leak-sensor/src/thirdreality/fingerprints.lua create mode 100644 drivers/SmartThings/zigbee-water-leak-sensor/src/zigbee-water-freeze/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-watering-kit/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zigbee-watering-kit/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zigbee-watering-kit/src/thirdreality/can_handle.lua create mode 100755 drivers/SmartThings/zigbee-window-treatment/profiles/window-shade-only.yml create mode 100755 drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/custom_clusters.lua create mode 100755 drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/init.lua create mode 100755 drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_only_HOPOsmart.lua create mode 100644 drivers/SmartThings/zwave-bulb/src/aeon-multiwhite-bulb/can_handle.lua create mode 100644 drivers/SmartThings/zwave-bulb/src/aeon-multiwhite-bulb/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-bulb/src/aeotec-led-bulb-6/can_handle.lua create mode 100644 drivers/SmartThings/zwave-bulb/src/fibaro-rgbw-controller/can_handle.lua create mode 100644 drivers/SmartThings/zwave-bulb/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zwave-bulb/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeon-meter/can_handle.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeon-meter/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-gen5-meter/can_handle.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/aeotec-gen5-meter/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/qubino-meter/can_handle.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/qubino-meter/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-electric-meter/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-sensor/profiles/zooz-zse42-water.yml create mode 100644 drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua create mode 100644 drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua create mode 100644 drivers/SmartThings/zwave-siren/src/aeon-siren/can_handle.lua create mode 100644 drivers/SmartThings/zwave-siren/src/aeotec-doorbell-siren/can_handle.lua create mode 100644 drivers/SmartThings/zwave-siren/src/aeotec-doorbell-siren/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-siren/src/apiv6_bugfix/can_handle.lua create mode 100644 drivers/SmartThings/zwave-siren/src/ecolink-wireless-siren/can_handle.lua create mode 100644 drivers/SmartThings/zwave-siren/src/ecolink-wireless-siren/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-siren/src/fortrezz/can_handle.lua create mode 100644 drivers/SmartThings/zwave-siren/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zwave-siren/src/multifunctional-siren/can_handle.lua create mode 100644 drivers/SmartThings/zwave-siren/src/multifunctional-siren/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-siren/src/philio-sound-siren/can_handle.lua create mode 100644 drivers/SmartThings/zwave-siren/src/philio-sound-siren/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-siren/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-siren/src/utilitech-siren/can_handle.lua create mode 100644 drivers/SmartThings/zwave-siren/src/yale-siren/can_handle.lua create mode 100644 drivers/SmartThings/zwave-siren/src/zipato-siren/can_handle.lua create mode 100644 drivers/SmartThings/zwave-siren/src/zwave-sound-sensor/can_handle.lua create mode 100644 drivers/SmartThings/zwave-siren/src/zwave-sound-sensor/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml create mode 100644 drivers/SmartThings/zwave-switch/profiles/rgbw-bulb.yml create mode 100644 drivers/SmartThings/zwave-switch/src/aeon-smart-strip/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/aeotec-heavy-duty/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/aeotec-smart-switch/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/dawon-smart-plug/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/dawon-wall-smart-switch/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/dawon-wall-smart-switch/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-switch/src/eaton-5-scene-keypad/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/eaton-accessory-dimmer/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/eaton-anyplace-switch/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/ecolink-switch/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/ecolink-switch/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-switch/src/fibaro-double-switch/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/fibaro-single-switch/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/fibaro-wall-plug-us/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/inovelli-2-channel-smart-plug/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/inovelli-2-channel-smart-plug/fingerprints.lua delete mode 100644 drivers/SmartThings/zwave-switch/src/inovelli-LED/init.lua delete mode 100644 drivers/SmartThings/zwave-switch/src/inovelli-LED/inovelli-lzw31sn/init.lua create mode 100644 drivers/SmartThings/zwave-switch/src/inovelli/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/inovelli/init.lua create mode 100644 drivers/SmartThings/zwave-switch/src/inovelli/lzw31-sn/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/inovelli/lzw31-sn/init.lua create mode 100644 drivers/SmartThings/zwave-switch/src/inovelli/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-switch/src/inovelli/vzw32-sn/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/inovelli/vzw32-sn/init.lua create mode 100644 drivers/SmartThings/zwave-switch/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zwave-switch/src/multi-metering-switch/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/multi-metering-switch/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-switch/src/multichannel-device/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/qubino-switches/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/qubino-switches/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/qubino-din-dimmer/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1-relay/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1d-relay/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-2-relay/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-switch/src/qubino-switches/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua create mode 100644 drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua create mode 100644 drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua create mode 100644 drivers/SmartThings/zwave-switch/src/zooz-power-strip/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/zooz-zen-30-dimmer-relay/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/zwave-dual-switch/can_handle.lua create mode 100644 drivers/SmartThings/zwave-switch/src/zwave-dual-switch/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/aeotec-radiator-thermostat/can_handle.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/can_handle.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/ct100-thermostat/can_handle.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/ct100-thermostat/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/fibaro-heat-controller/can_handle.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/fibaro-heat-controller/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/popp-radiator-thermostat/can_handle.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/qubino-flush-thermostat/can_handle.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/qubino-flush-thermostat/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/stelpro-ki-thermostat/can_handle.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/stelpro-ki-thermostat/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/thermostat-heating-battery/can_handle.lua create mode 100644 drivers/SmartThings/zwave-thermostat/src/thermostat-heating-battery/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/aeotec-nano-shutter/can_handle.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/aeotec-nano-shutter/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/can_handle.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3/can_handle.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3/fingerprints.lua rename drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/{v3.lua => v3/init.lua} (84%) create mode 100644 drivers/SmartThings/zwave-window-treatment/src/lazy_load_subdriver.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/springs-window-fashion-shade/can_handle.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/springs-window-fashion-shade/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/sub_drivers.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/can_handle.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fibaro-roller-shutter/can_handle.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fibaro-roller-shutter/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/qubino-flush-shutter/can_handle.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/qubino-flush-shutter/fingerprints.lua create mode 100644 drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/sub_drivers.lua create mode 100755 tools/pre-commit diff --git a/.gitignore b/.gitignore index c5bbdc6f99..72da442d81 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,7 @@ lua_libs-api_* *.out tools/test_output/* tools/coverage_output/* +tools/coverage_output_html/ +tools/__pycache__/ .DS_Store .venv/ diff --git a/README.md b/README.md index 3eb6fb07cc..c87d2c66c2 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ By submitting a pull request, you represent that you have the right to license your contribution to SmartThings and agree by submitting your patch that your contributions are licensed under the [Apache 2.0 license](LICENSE). Before submitting your pull request, please make sure you have tested your changes and that -they follow the project guidelines for [contributing code](https://developer.smartthings.com/docs/devices/hub-connected/certify-your-device#code-formatting-and-submission-criteria). +they follow the project guidelines for [contributing code](https://developer.smartthings.com/docs/devices/hub-connected/code-formatting-criteria). Before contributions can be merged, all contributors must agree to the [SmartThings Individual Contributor License diff --git a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/init.lua b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/init.lua index c7057f82e4..fff466fea1 100644 --- a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/init.lua +++ b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/init.lua @@ -1,3 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local cluster_base = require "st.matter.cluster_base" local ActivatedCarbonFilterMonitoringServerAttributes = require "ActivatedCarbonFilterMonitoring.server.attributes" local ActivatedCarbonFilterMonitoringTypes = require "ActivatedCarbonFilterMonitoring.types" diff --git a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua index ef3fb00a6a..9f34e51dbf 100644 --- a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua +++ b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua @@ -1,16 +1,6 @@ --- 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. +-- Copyright 2024 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -123,4 +113,3 @@ end setmetatable(AttributeList, {__call = AttributeList.new_value, __index = AttributeList.base_type}) return AttributeList - diff --git a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua index dd6fe1ecfb..71170e772f 100644 --- a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua +++ b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua @@ -1,16 +1,6 @@ --- 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. +-- Copyright 2024 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -113,4 +103,3 @@ end setmetatable(ChangeIndication, {__call = ChangeIndication.new_value, __index = ChangeIndication.base_type}) return ChangeIndication - diff --git a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/init.lua b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/init.lua index 2ff8e6e89a..80e6e6b86a 100644 --- a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/init.lua +++ b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-appliance/src/DishwasherAlarm/init.lua b/drivers/SmartThings/matter-appliance/src/DishwasherAlarm/init.lua index 9a6be67486..382197bc60 100644 --- a/drivers/SmartThings/matter-appliance/src/DishwasherAlarm/init.lua +++ b/drivers/SmartThings/matter-appliance/src/DishwasherAlarm/init.lua @@ -1,3 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local cluster_base = require "st.matter.cluster_base" local DishwasherAlarmServerAttributes = require "DishwasherAlarm.server.attributes" local DishwasherAlarmTypes = require "DishwasherAlarm.types" diff --git a/drivers/SmartThings/matter-appliance/src/DishwasherAlarm/types/init.lua b/drivers/SmartThings/matter-appliance/src/DishwasherAlarm/types/init.lua index d5c4bba1fb..3ddec58dcc 100644 --- a/drivers/SmartThings/matter-appliance/src/DishwasherAlarm/types/init.lua +++ b/drivers/SmartThings/matter-appliance/src/DishwasherAlarm/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-appliance/src/DishwasherMode/init.lua b/drivers/SmartThings/matter-appliance/src/DishwasherMode/init.lua index 913f001fdb..0bb22e1884 100644 --- a/drivers/SmartThings/matter-appliance/src/DishwasherMode/init.lua +++ b/drivers/SmartThings/matter-appliance/src/DishwasherMode/init.lua @@ -1,3 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local cluster_base = require "st.matter.cluster_base" local DishwasherModeServerAttributes = require "DishwasherMode.server.attributes" local DishwasherModeServerCommands = require "DishwasherMode.server.commands" diff --git a/drivers/SmartThings/matter-appliance/src/DishwasherMode/types/init.lua b/drivers/SmartThings/matter-appliance/src/DishwasherMode/types/init.lua index dd07b2f752..34d510160e 100644 --- a/drivers/SmartThings/matter-appliance/src/DishwasherMode/types/init.lua +++ b/drivers/SmartThings/matter-appliance/src/DishwasherMode/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/init.lua b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/init.lua index 21795104b7..b6df5d554c 100644 --- a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/init.lua +++ b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/init.lua @@ -1,3 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local cluster_base = require "st.matter.cluster_base" local HepaFilterMonitoringServerAttributes = require "HepaFilterMonitoring.server.attributes" local HepaFilterMonitoringTypes = require "HepaFilterMonitoring.types" diff --git a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/AttributeList.lua index c4f817e428..6fdd2e5350 100644 --- a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/AttributeList.lua +++ b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/AttributeList.lua @@ -1,16 +1,6 @@ --- 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. +-- Copyright 2024 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -123,4 +113,3 @@ end setmetatable(AttributeList, {__call = AttributeList.new_value, __index = AttributeList.base_type}) return AttributeList - diff --git a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua index 06a80153e3..b205026e1b 100644 --- a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua +++ b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua @@ -1,16 +1,6 @@ --- 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. +-- Copyright 2024 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -113,4 +103,3 @@ end setmetatable(ChangeIndication, {__call = ChangeIndication.new_value, __index = ChangeIndication.base_type}) return ChangeIndication - diff --git a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/init.lua b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/init.lua index 77aca088ff..f4eea9ee26 100644 --- a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/init.lua +++ b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-appliance/src/LaundryWasherControls/init.lua b/drivers/SmartThings/matter-appliance/src/LaundryWasherControls/init.lua index 63bf4168e7..c6ff01850b 100644 --- a/drivers/SmartThings/matter-appliance/src/LaundryWasherControls/init.lua +++ b/drivers/SmartThings/matter-appliance/src/LaundryWasherControls/init.lua @@ -1,3 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local LaundryWasherControlsServerAttributes = require "LaundryWasherControls.server.attributes" local LaundryWasherControlsTypes = require "LaundryWasherControls.types" diff --git a/drivers/SmartThings/matter-appliance/src/LaundryWasherControls/types/init.lua b/drivers/SmartThings/matter-appliance/src/LaundryWasherControls/types/init.lua index e8a2f0ac53..4ba9f25726 100644 --- a/drivers/SmartThings/matter-appliance/src/LaundryWasherControls/types/init.lua +++ b/drivers/SmartThings/matter-appliance/src/LaundryWasherControls/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-appliance/src/LaundryWasherMode/init.lua b/drivers/SmartThings/matter-appliance/src/LaundryWasherMode/init.lua index aa030e58cc..e64bde94a0 100644 --- a/drivers/SmartThings/matter-appliance/src/LaundryWasherMode/init.lua +++ b/drivers/SmartThings/matter-appliance/src/LaundryWasherMode/init.lua @@ -1,3 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local cluster_base = require "st.matter.cluster_base" local LaundryWasherModeServerAttributes = require "LaundryWasherMode.server.attributes" local LaundryWasherModeServerCommands = require "LaundryWasherMode.server.commands" diff --git a/drivers/SmartThings/matter-appliance/src/LaundryWasherMode/types/init.lua b/drivers/SmartThings/matter-appliance/src/LaundryWasherMode/types/init.lua index 057fe27dbe..d201d4ba36 100644 --- a/drivers/SmartThings/matter-appliance/src/LaundryWasherMode/types/init.lua +++ b/drivers/SmartThings/matter-appliance/src/LaundryWasherMode/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/init.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/init.lua index 697aa3fd05..5c333dea1e 100644 --- a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/init.lua +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/init.lua @@ -1,3 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local cluster_base = require "st.matter.cluster_base" local MicrowaveOvenControlServerAttributes = require "MicrowaveOvenControl.server.attributes" local MicrowaveOvenControlServerCommands = require "MicrowaveOvenControl.server.commands" @@ -81,4 +85,4 @@ setmetatable(MicrowaveOvenControl.commands, command_helper_mt) setmetatable(MicrowaveOvenControl, {__index = cluster_base}) -return MicrowaveOvenControl \ No newline at end of file +return MicrowaveOvenControl diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/types/init.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/types/init.lua index 29c27b80ae..1d11195ee2 100644 --- a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/types/init.lua +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/init.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/init.lua index fdbbf4c443..dca8ff3ed5 100644 --- a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/init.lua +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/init.lua @@ -1,3 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local cluster_base = require "st.matter.cluster_base" local MicrowaveOvenModeServerAttributes = require "MicrowaveOvenMode.server.attributes" local MicrowaveOvenModeTypes = require "MicrowaveOvenMode.types" diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/init.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/init.lua index 412d69cabd..e67593141b 100644 --- a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/init.lua +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-appliance/src/OperationalState/init.lua b/drivers/SmartThings/matter-appliance/src/OperationalState/init.lua index 5239cc1cfe..7be274fb8d 100644 --- a/drivers/SmartThings/matter-appliance/src/OperationalState/init.lua +++ b/drivers/SmartThings/matter-appliance/src/OperationalState/init.lua @@ -1,3 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local cluster_base = require "st.matter.cluster_base" local OperationalStateServerAttributes = require "OperationalState.server.attributes" local OperationalStateServerCommands = require "OperationalState.server.commands" diff --git a/drivers/SmartThings/matter-appliance/src/OperationalState/types/init.lua b/drivers/SmartThings/matter-appliance/src/OperationalState/types/init.lua index 8c735ed06a..18bbca0824 100644 --- a/drivers/SmartThings/matter-appliance/src/OperationalState/types/init.lua +++ b/drivers/SmartThings/matter-appliance/src/OperationalState/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-appliance/src/OvenMode/init.lua b/drivers/SmartThings/matter-appliance/src/OvenMode/init.lua index 76e1817dc1..4fc7c11376 100644 --- a/drivers/SmartThings/matter-appliance/src/OvenMode/init.lua +++ b/drivers/SmartThings/matter-appliance/src/OvenMode/init.lua @@ -1,3 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local cluster_base = require "st.matter.cluster_base" local OvenModeServerAttributes = require "OvenMode.server.attributes" local OvenModeServerCommands = require "OvenMode.server.commands" diff --git a/drivers/SmartThings/matter-appliance/src/OvenMode/types/init.lua b/drivers/SmartThings/matter-appliance/src/OvenMode/types/init.lua index 8bd0777339..b3565aaa2b 100644 --- a/drivers/SmartThings/matter-appliance/src/OvenMode/types/init.lua +++ b/drivers/SmartThings/matter-appliance/src/OvenMode/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-appliance/src/RefrigeratorAlarm/init.lua b/drivers/SmartThings/matter-appliance/src/RefrigeratorAlarm/init.lua index a03452193c..1c9610b520 100644 --- a/drivers/SmartThings/matter-appliance/src/RefrigeratorAlarm/init.lua +++ b/drivers/SmartThings/matter-appliance/src/RefrigeratorAlarm/init.lua @@ -1,3 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local RefrigeratorAlarmServerAttributes = require "RefrigeratorAlarm.server.attributes" local RefrigeratorAlarmTypes = require "RefrigeratorAlarm.types" diff --git a/drivers/SmartThings/matter-appliance/src/RefrigeratorAlarm/types/init.lua b/drivers/SmartThings/matter-appliance/src/RefrigeratorAlarm/types/init.lua index 111371cb52..d8ebdc1ed3 100644 --- a/drivers/SmartThings/matter-appliance/src/RefrigeratorAlarm/types/init.lua +++ b/drivers/SmartThings/matter-appliance/src/RefrigeratorAlarm/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-appliance/src/RefrigeratorAndTemperatureControlledCabinetMode/init.lua b/drivers/SmartThings/matter-appliance/src/RefrigeratorAndTemperatureControlledCabinetMode/init.lua index f85f5e4e31..345b7b57b1 100644 --- a/drivers/SmartThings/matter-appliance/src/RefrigeratorAndTemperatureControlledCabinetMode/init.lua +++ b/drivers/SmartThings/matter-appliance/src/RefrigeratorAndTemperatureControlledCabinetMode/init.lua @@ -1,3 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local cluster_base = require "st.matter.cluster_base" local RefrigeratorAndTemperatureControlledCabinetModeServerAttributes = require "RefrigeratorAndTemperatureControlledCabinetMode.server.attributes" local RefrigeratorAndTemperatureControlledCabinetModeServerCommands = require "RefrigeratorAndTemperatureControlledCabinetMode.server.commands" diff --git a/drivers/SmartThings/matter-appliance/src/RefrigeratorAndTemperatureControlledCabinetMode/types/init.lua b/drivers/SmartThings/matter-appliance/src/RefrigeratorAndTemperatureControlledCabinetMode/types/init.lua index d03e0d01d7..0079c8e25e 100644 --- a/drivers/SmartThings/matter-appliance/src/RefrigeratorAndTemperatureControlledCabinetMode/types/init.lua +++ b/drivers/SmartThings/matter-appliance/src/RefrigeratorAndTemperatureControlledCabinetMode/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-appliance/src/TemperatureControl/init.lua b/drivers/SmartThings/matter-appliance/src/TemperatureControl/init.lua index 2d453d8662..2ab1c94056 100644 --- a/drivers/SmartThings/matter-appliance/src/TemperatureControl/init.lua +++ b/drivers/SmartThings/matter-appliance/src/TemperatureControl/init.lua @@ -1,3 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local cluster_base = require "st.matter.cluster_base" local TemperatureControlServerAttributes = require "TemperatureControl.server.attributes" local TemperatureControlServerCommands = require "TemperatureControl.server.commands" diff --git a/drivers/SmartThings/matter-appliance/src/TemperatureControl/types/init.lua b/drivers/SmartThings/matter-appliance/src/TemperatureControl/types/init.lua index feeafbc78b..53c77d6ff9 100644 --- a/drivers/SmartThings/matter-appliance/src/TemperatureControl/types/init.lua +++ b/drivers/SmartThings/matter-appliance/src/TemperatureControl/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-appliance/src/common-utils.lua b/drivers/SmartThings/matter-appliance/src/common-utils.lua index 1250d40912..d35ee2b6fc 100644 --- a/drivers/SmartThings/matter-appliance/src/common-utils.lua +++ b/drivers/SmartThings/matter-appliance/src/common-utils.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" diff --git a/drivers/SmartThings/matter-appliance/src/embedded-cluster-utils.lua b/drivers/SmartThings/matter-appliance/src/embedded-cluster-utils.lua index 52d9909dff..5a5a89af73 100644 --- a/drivers/SmartThings/matter-appliance/src/embedded-cluster-utils.lua +++ b/drivers/SmartThings/matter-appliance/src/embedded-cluster-utils.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local clusters = require "st.matter.clusters" local utils = require "st.utils" diff --git a/drivers/SmartThings/matter-appliance/src/init.lua b/drivers/SmartThings/matter-appliance/src/init.lua index d107a48fc5..044da51e7c 100644 --- a/drivers/SmartThings/matter-appliance/src/init.lua +++ b/drivers/SmartThings/matter-appliance/src/init.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local MatterDriver = require "st.matter.driver" local capabilities = require "st.capabilities" @@ -307,15 +296,7 @@ local matter_driver_template = { capabilities.fanSpeedPercent, capabilities.windMode }, - sub_drivers = { - require("matter-cook-top"), - require("matter-dishwasher"), - require("matter-extractor-hood"), - require("matter-laundry"), - require("matter-microwave-oven"), - require("matter-oven"), - require("matter-refrigerator") - } + sub_drivers = require("sub_drivers"), } local matter_driver = MatterDriver("matter-appliance", matter_driver_template) diff --git a/drivers/SmartThings/matter-appliance/src/lazy_load_subdriver.lua b/drivers/SmartThings/matter-appliance/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..a04740d267 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/lazy_load_subdriver.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(sub_driver_name) + local MatterDriver = require "st.matter.driver" + local version = require "version" + if version.api >= 16 then + return MatterDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return MatterDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end +end diff --git a/drivers/SmartThings/matter-appliance/src/matter-cook-top/can_handle.lua b/drivers/SmartThings/matter-appliance/src/matter-cook-top/can_handle.lua new file mode 100644 index 0000000000..d2899d279a --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/matter-cook-top/can_handle.lua @@ -0,0 +1,21 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_cook_top_device(opts, driver, device, ...) + local common_utils = require "common-utils" + local COOK_TOP_DEVICE_TYPE_ID = 0x0078 + local OVEN_DEVICE_ID = 0x007B + + local cook_top_eps = common_utils.get_endpoints_for_dt(device, COOK_TOP_DEVICE_TYPE_ID) + local oven_eps = common_utils.get_endpoints_for_dt(device, OVEN_DEVICE_ID) + -- we want to skip lifecycle events in cases where the device is an oven with a composed cook-top device + if (#oven_eps > 0) and opts.dispatcher_class == "DeviceLifecycleDispatcher" then + return false + end + if #cook_top_eps > 0 then + return true, require("matter-cook-top") + end + return false +end + +return is_cook_top_device diff --git a/drivers/SmartThings/matter-appliance/src/matter-cook-top/init.lua b/drivers/SmartThings/matter-appliance/src/matter-cook-top/init.lua index 0a8f9f3182..d0ce48dc18 100644 --- a/drivers/SmartThings/matter-appliance/src/matter-cook-top/init.lua +++ b/drivers/SmartThings/matter-appliance/src/matter-cook-top/init.lua @@ -1,97 +1,73 @@ --- Copyright 2025 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 clusters = require "st.matter.clusters" -local common_utils = require "common-utils" -local embedded_cluster_utils = require "embedded-cluster-utils" -local version = require "version" - -if version.api < 10 then - clusters.TemperatureControl = require "TemperatureControl" -end - -local COOK_SURFACE_DEVICE_TYPE_ID = 0x0077 -local COOK_TOP_DEVICE_TYPE_ID = 0x0078 -local OVEN_DEVICE_ID = 0x007B - -local function table_contains(tab, val) - for _, tab_val in ipairs(tab) do - if tab_val == val then - return true - end - end - return false -end - -local function device_added(driver, device) - local cook_surface_endpoints = common_utils.get_endpoints_for_dt(device, COOK_SURFACE_DEVICE_TYPE_ID) - local componentToEndpointMap = { - ["cookSurfaceOne"] = cook_surface_endpoints[1], - ["cookSurfaceTwo"] = cook_surface_endpoints[2] - } - device:set_field(common_utils.COMPONENT_TO_ENDPOINT_MAP, componentToEndpointMap, { persist = true }) -end - -local function do_configure(driver, device) - local cook_surface_endpoints = common_utils.get_endpoints_for_dt(device, COOK_SURFACE_DEVICE_TYPE_ID) - - local tl_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, - { feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_LEVEL }) - - local profile_name - if #cook_surface_endpoints > 0 then - profile_name = "cook-surface-one" - if table_contains(tl_eps, cook_surface_endpoints[1]) then - profile_name = profile_name .. "-tl" - end - - -- we only support up to two cook surfaces - if #cook_surface_endpoints > 1 then - profile_name = profile_name .. "-cook-surface-two" - if table_contains(tl_eps, cook_surface_endpoints[2]) then - profile_name = profile_name .. "-tl" - end - end - end - - if profile_name then - device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) - device:try_update_metadata({ profile = profile_name }) - end -end - -local function is_cook_top_device(opts, driver, device, ...) - local cook_top_eps = common_utils.get_endpoints_for_dt(device, COOK_TOP_DEVICE_TYPE_ID) - local oven_eps = common_utils.get_endpoints_for_dt(device, OVEN_DEVICE_ID) - -- we want to skip lifecycle events in cases where the device is an oven with a composed cook-top device - if (#oven_eps > 0) and opts.dispatcher_class == "DeviceLifecycleDispatcher" then - return false - end - if #cook_top_eps > 0 then - return true - end - return false -end - --- Matter Handlers -- -local matter_cook_top_handler = { - NAME = "matter-cook-top", - lifecycle_handlers = { - added = device_added, - doConfigure = do_configure - }, - can_handle = is_cook_top_device -} - -return matter_cook_top_handler +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local clusters = require "st.matter.clusters" +local common_utils = require "common-utils" +local embedded_cluster_utils = require "embedded-cluster-utils" +local version = require "version" + +if version.api < 10 then + clusters.TemperatureControl = require "TemperatureControl" +end + +local COOK_SURFACE_DEVICE_TYPE_ID = 0x0077 + +local function table_contains(tab, val) + for _, tab_val in ipairs(tab) do + if tab_val == val then + return true + end + end + return false +end + +local function device_added(driver, device) + local cook_surface_endpoints = common_utils.get_endpoints_for_dt(device, COOK_SURFACE_DEVICE_TYPE_ID) + local componentToEndpointMap = { + ["cookSurfaceOne"] = cook_surface_endpoints[1], + ["cookSurfaceTwo"] = cook_surface_endpoints[2] + } + device:set_field(common_utils.COMPONENT_TO_ENDPOINT_MAP, componentToEndpointMap, { persist = true }) +end + +local function do_configure(driver, device) + local cook_surface_endpoints = common_utils.get_endpoints_for_dt(device, COOK_SURFACE_DEVICE_TYPE_ID) + + local tl_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, + { feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_LEVEL }) + + local profile_name + if #cook_surface_endpoints > 0 then + profile_name = "cook-surface-one" + if table_contains(tl_eps, cook_surface_endpoints[1]) then + profile_name = profile_name .. "-tl" + end + + -- we only support up to two cook surfaces + if #cook_surface_endpoints > 1 then + profile_name = profile_name .. "-cook-surface-two" + if table_contains(tl_eps, cook_surface_endpoints[2]) then + profile_name = profile_name .. "-tl" + end + end + end + + if profile_name then + device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) + device:try_update_metadata({ profile = profile_name }) + end +end + + +-- Matter Handlers -- +local matter_cook_top_handler = { + NAME = "matter-cook-top", + lifecycle_handlers = { + added = device_added, + doConfigure = do_configure + }, + can_handle = require("matter-cook-top.can_handle"), +} + +return matter_cook_top_handler diff --git a/drivers/SmartThings/matter-appliance/src/matter-dishwasher/can_handle.lua b/drivers/SmartThings/matter-appliance/src/matter-dishwasher/can_handle.lua new file mode 100644 index 0000000000..37226f3f3e --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/matter-dishwasher/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local function is_matter_dishwasher(opts, driver, device) + local DISHWASHER_DEVICE_TYPE_ID = 0x0075 + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == DISHWASHER_DEVICE_TYPE_ID then + return true, require("matter-dishwasher") + end + end + end + return false +end + +return is_matter_dishwasher diff --git a/drivers/SmartThings/matter-appliance/src/matter-dishwasher/init.lua b/drivers/SmartThings/matter-appliance/src/matter-dishwasher/init.lua index 96339fafc5..2e01afe7fb 100644 --- a/drivers/SmartThings/matter-appliance/src/matter-dishwasher/init.lua +++ b/drivers/SmartThings/matter-appliance/src/matter-dishwasher/init.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" @@ -18,8 +7,6 @@ local common_utils = require "common-utils" local embedded_cluster_utils = require "embedded-cluster-utils" local version = require "version" -local DISHWASHER_DEVICE_TYPE_ID = 0x0075 - if version.api < 10 then clusters.DishwasherAlarm = require "DishwasherAlarm" clusters.DishwasherMode = require "DishwasherMode" @@ -36,16 +23,6 @@ local OPERATIONAL_STATE_COMMAND_MAP = { local SUPPORTED_DISHWASHER_MODES = "__supported_dishwasher_modes" -local function is_matter_dishwasher(opts, driver, device) - for _, ep in ipairs(device.endpoints) do - for _, dt in ipairs(ep.device_types) do - if dt.device_type_id == DISHWASHER_DEVICE_TYPE_ID then - return true - end - end - end - return false -end -- Lifecycle Handlers -- local function do_configure(driver, device) @@ -267,7 +244,7 @@ local matter_dishwasher_handler = { [capabilities.operationalState.commands.resume.NAME] = handle_operational_state_resume } }, - can_handle = is_matter_dishwasher + can_handle = require("matter-dishwasher.can_handle"), } return matter_dishwasher_handler diff --git a/drivers/SmartThings/matter-appliance/src/matter-extractor-hood/can_handle.lua b/drivers/SmartThings/matter-appliance/src/matter-extractor-hood/can_handle.lua new file mode 100644 index 0000000000..d8ec2f9afd --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/matter-extractor-hood/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_matter_extractor_hood(opts, driver, device) + local EXTRACTOR_HOOD_DEVICE_TYPE_ID = 0x007A + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == EXTRACTOR_HOOD_DEVICE_TYPE_ID then + return true, require("matter-extractor-hood") + end + end + end + return false +end + +return is_matter_extractor_hood diff --git a/drivers/SmartThings/matter-appliance/src/matter-extractor-hood/init.lua b/drivers/SmartThings/matter-appliance/src/matter-extractor-hood/init.lua index 57eef3ac94..f83681e726 100644 --- a/drivers/SmartThings/matter-appliance/src/matter-extractor-hood/init.lua +++ b/drivers/SmartThings/matter-appliance/src/matter-extractor-hood/init.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" @@ -77,17 +67,6 @@ local function do_configure(driver, device) end -- Matter Handlers -- -local function is_matter_extractor_hood(opts, driver, device) - for _, ep in ipairs(device.endpoints) do - for _, dt in ipairs(ep.device_types) do - if dt.device_type_id == EXTRACTOR_HOOD_DEVICE_TYPE_ID then - return true - end - end - end - return false -end - local function fan_mode_handler(driver, device, ib, response) if ib.data.value == clusters.FanControl.attributes.FanMode.OFF then device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanMode.fanMode.off()) @@ -309,7 +288,7 @@ local matter_extractor_hood_handler = { [capabilities.windMode.commands.setWindMode.NAME] = set_wind_mode } }, - can_handle = is_matter_extractor_hood + can_handle = require("matter-extractor-hood.can_handle"), } return matter_extractor_hood_handler diff --git a/drivers/SmartThings/matter-appliance/src/matter-laundry/can_handle.lua b/drivers/SmartThings/matter-appliance/src/matter-laundry/can_handle.lua new file mode 100644 index 0000000000..dd71f12e9e --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/matter-laundry/can_handle.lua @@ -0,0 +1,19 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_matter_laundry_device(opts, driver, device) + local LAUNDRY_WASHER_DEVICE_TYPE_ID = 0x0073 + local LAUNDRY_DRYER_DEVICE_TYPE_ID = 0x007C + local LAUNDRY_DEVICE_TYPE_ID= "__laundry_device_type_id" + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == LAUNDRY_WASHER_DEVICE_TYPE_ID or dt.device_type_id == LAUNDRY_DRYER_DEVICE_TYPE_ID then + device:set_field(LAUNDRY_DEVICE_TYPE_ID, dt.device_type_id, {persist = true}) + return dt.device_type_id, require("matter-laundry") + end + end + end + return false +end + +return is_matter_laundry_device diff --git a/drivers/SmartThings/matter-appliance/src/matter-laundry/init.lua b/drivers/SmartThings/matter-appliance/src/matter-laundry/init.lua index 8c49291da0..26b9efb606 100644 --- a/drivers/SmartThings/matter-appliance/src/matter-laundry/init.lua +++ b/drivers/SmartThings/matter-appliance/src/matter-laundry/init.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" @@ -47,23 +37,12 @@ local SUPPORTED_LAUNDRY_WASHER_MODES = "__supported_laundry_washer_modes" local SUPPORTED_LAUNDRY_WASHER_SPIN_SPEEDS = "__supported_laundry_spin_speeds" local SUPPORTED_LAUNDRY_WASHER_RINSES = "__supported_laundry_washer_rinses" -local function is_matter_laundry_device(opts, driver, device) - for _, ep in ipairs(device.endpoints) do - for _, dt in ipairs(ep.device_types) do - if dt.device_type_id == LAUNDRY_WASHER_DEVICE_TYPE_ID or dt.device_type_id == LAUNDRY_DRYER_DEVICE_TYPE_ID then - device:set_field(LAUNDRY_DEVICE_TYPE_ID, dt.device_type_id, {persist = true}) - return dt.device_type_id - end - end - end - return false -end - -- Lifecycle Handlers -- local function do_configure(driver, device) local tn_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, {feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_NUMBER}) local tl_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, {feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_LEVEL}) - local device_type = is_matter_laundry_device({}, driver, device) + local is_matter_laundry_device = require("matter-laundry.can_handle") + local device_type, _ = is_matter_laundry_device({}, driver, device) local profile_name = "laundry" if (device_type == LAUNDRY_WASHER_DEVICE_TYPE_ID) then profile_name = profile_name.."-washer" @@ -316,7 +295,7 @@ local matter_laundry_handler = { [capabilities.operationalState.commands.resume.NAME] = handle_operational_state_resume } }, - can_handle = is_matter_laundry_device + can_handle = require("matter-laundry.can_handle"), } return matter_laundry_handler diff --git a/drivers/SmartThings/matter-appliance/src/matter-microwave-oven/can_handle.lua b/drivers/SmartThings/matter-appliance/src/matter-microwave-oven/can_handle.lua new file mode 100644 index 0000000000..ebdda29863 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/matter-microwave-oven/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_matter_mircowave_oven(opts, driver, device) + local MICROWAVE_OVEN_DEVICE_TYPE_ID = 0x0079 + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == MICROWAVE_OVEN_DEVICE_TYPE_ID then + return true, require("matter-microwave-oven") + end + end + end + return false +end + +return is_matter_mircowave_oven diff --git a/drivers/SmartThings/matter-appliance/src/matter-microwave-oven/init.lua b/drivers/SmartThings/matter-appliance/src/matter-microwave-oven/init.lua index 6ebd6a4c69..342723656a 100644 --- a/drivers/SmartThings/matter-appliance/src/matter-microwave-oven/init.lua +++ b/drivers/SmartThings/matter-appliance/src/matter-microwave-oven/init.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" @@ -34,7 +24,6 @@ local OPERATIONAL_STATE_COMMAND_MAP = { [clusters.OperationalState.commands.Resume.ID] = "resume", } -local MICROWAVE_OVEN_DEVICE_TYPE_ID = 0x0079 local DEFAULT_COOKING_MODE = 0 local DEFAULT_COOKING_TIME = 30 local MICROWAVE_OVEN_SUPPORTED_MODES_KEY = "__microwave_oven_supported_modes__" @@ -45,16 +34,6 @@ local function device_init(driver, device) device:send(clusters.MicrowaveOvenControl.attributes.MaxCookTime:read(device, device.MATTER_DEFAULT_ENDPOINT)) end -local function is_matter_mircowave_oven(opts, driver, device) - for _, ep in ipairs(device.endpoints) do - for _, dt in ipairs(ep.device_types) do - if dt.device_type_id == MICROWAVE_OVEN_DEVICE_TYPE_ID then - return true - end - end - end - return false -end local function get_last_set_cooking_parameters(device) local cookingTime = device:get_latest_state("main", capabilities.cookTime.ID, capabilities.cookTime.cookTime.NAME) or DEFAULT_COOKING_TIME @@ -271,7 +250,7 @@ local matter_microwave_oven = { capabilities.mode, capabilities.cookTime }, - can_handle = is_matter_mircowave_oven + can_handle = require("matter-microwave-oven.can_handle"), } return matter_microwave_oven diff --git a/drivers/SmartThings/matter-appliance/src/matter-oven/can_handle.lua b/drivers/SmartThings/matter-appliance/src/matter-oven/can_handle.lua new file mode 100644 index 0000000000..a5bb61f9b8 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/matter-oven/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_oven_device(opts, driver, device) + local common_utils = require "common-utils" + local OVEN_DEVICE_ID = 0x007B + local oven_eps = common_utils.get_endpoints_for_dt(device, OVEN_DEVICE_ID) + if #oven_eps > 0 then + return true, require("matter-oven") + end + return false +end + +return is_oven_device diff --git a/drivers/SmartThings/matter-appliance/src/matter-oven/init.lua b/drivers/SmartThings/matter-appliance/src/matter-oven/init.lua index 9847f24191..6c31504a6a 100644 --- a/drivers/SmartThings/matter-appliance/src/matter-oven/init.lua +++ b/drivers/SmartThings/matter-appliance/src/matter-oven/init.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" @@ -28,18 +18,10 @@ end local SUPPORTED_OVEN_MODES_MAP = "__supported_oven_modes_map_key_" -local OVEN_DEVICE_ID = 0x007B local COOK_SURFACE_DEVICE_TYPE_ID = 0x0077 local COOK_TOP_DEVICE_TYPE_ID = 0x0078 local TCC_DEVICE_TYPE_ID = 0x0071 -local function is_oven_device(opts, driver, device) - local oven_eps = common_utils.get_endpoints_for_dt(device, OVEN_DEVICE_ID) - if #oven_eps > 0 then - return true - end - return false -end -- Lifecycle Handlers -- local function device_added(driver, device) @@ -141,7 +123,7 @@ local matter_oven_handler = { [capabilities.temperatureSetpoint.commands.setTemperatureSetpoint.NAME] = handle_temperature_setpoint } }, - can_handle = is_oven_device + can_handle = require("matter-oven.can_handle"), } return matter_oven_handler diff --git a/drivers/SmartThings/matter-appliance/src/matter-refrigerator/can_handle.lua b/drivers/SmartThings/matter-appliance/src/matter-refrigerator/can_handle.lua new file mode 100644 index 0000000000..376b32cc2d --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/matter-refrigerator/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_matter_refrigerator(opts, driver, device) + local REFRIGERATOR_DEVICE_TYPE_ID = 0x0070 + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == REFRIGERATOR_DEVICE_TYPE_ID then + return true, require("matter-refrigerator") + end + end + end + return false +end + +return is_matter_refrigerator diff --git a/drivers/SmartThings/matter-appliance/src/matter-refrigerator/init.lua b/drivers/SmartThings/matter-appliance/src/matter-refrigerator/init.lua index ec57c61505..c48412e849 100644 --- a/drivers/SmartThings/matter-appliance/src/matter-refrigerator/init.lua +++ b/drivers/SmartThings/matter-appliance/src/matter-refrigerator/init.lua @@ -1,16 +1,6 @@ --- 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. +-- Copyright 2023 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" @@ -18,7 +8,6 @@ local common_utils = require "common-utils" local embedded_cluster_utils = require "embedded-cluster-utils" local version = require "version" -local REFRIGERATOR_DEVICE_TYPE_ID = 0x0070 local TEMPERATURE_CONTROLLED_CABINET_DEVICE_TYPE_ID = 0x0071 if version.api < 10 then @@ -29,17 +18,6 @@ end local SUPPORTED_REFRIGERATOR_TCC_MODES_MAP = "__supported_refrigerator_tcc_modes_map" -local function is_matter_refrigerator(opts, driver, device) - for _, ep in ipairs(device.endpoints) do - for _, dt in ipairs(ep.device_types) do - if dt.device_type_id == REFRIGERATOR_DEVICE_TYPE_ID then - return true - end - end - end - return false -end - -- Lifecycle Handlers -- local function device_added(driver, device) local cabinet_eps = {} @@ -182,7 +160,7 @@ local matter_refrigerator_handler = { [capabilities.temperatureSetpoint.commands.setTemperatureSetpoint.NAME] = handle_temperature_setpoint } }, - can_handle = is_matter_refrigerator + can_handle = require("matter-refrigerator.can_handle"), } return matter_refrigerator_handler diff --git a/drivers/SmartThings/matter-appliance/src/sub_drivers.lua b/drivers/SmartThings/matter-appliance/src/sub_drivers.lua new file mode 100644 index 0000000000..e0ad824520 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/sub_drivers.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" +local sub_drivers = { + lazy_load_if_possible("matter-cook-top"), + lazy_load_if_possible("matter-dishwasher"), + lazy_load_if_possible("matter-extractor-hood"), + lazy_load_if_possible("matter-laundry"), + lazy_load_if_possible("matter-microwave-oven"), + lazy_load_if_possible("matter-oven"), + lazy_load_if_possible("matter-refrigerator"), +} +return sub_drivers diff --git a/drivers/SmartThings/matter-appliance/src/test/test_cook_top.lua b/drivers/SmartThings/matter-appliance/src/test/test_cook_top.lua index 9aa45f7951..4a65f8e882 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_cook_top.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_cook_top.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local t_utils = require "integration_test.utils" @@ -194,4 +184,4 @@ test.register_message_test( } ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua b/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua index c6923c3de4..f446d1149b 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_dishwasher.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" test.set_rpc_version(6) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_extractor_hood.lua b/drivers/SmartThings/matter-appliance/src/test/test_extractor_hood.lua index 91339b78ca..b8d18a3ab4 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_extractor_hood.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_extractor_hood.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/matter-appliance/src/test/test_laundry_dryer.lua b/drivers/SmartThings/matter-appliance/src/test/test_laundry_dryer.lua index 10e0de0f5e..9049b5f64e 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_laundry_dryer.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_laundry_dryer.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" test.set_rpc_version(6) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_laundry_washer.lua b/drivers/SmartThings/matter-appliance/src/test/test_laundry_washer.lua index 0334bf61cb..6f7ef3ee5c 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_laundry_washer.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_laundry_washer.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" test.set_rpc_version(6) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_matter_appliance_rpc_5.lua b/drivers/SmartThings/matter-appliance/src/test/test_matter_appliance_rpc_5.lua index 8f9a8c81d8..d8faa1e5ee 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_matter_appliance_rpc_5.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_matter_appliance_rpc_5.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" test.set_rpc_version(5) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_microwave_oven.lua b/drivers/SmartThings/matter-appliance/src/test/test_microwave_oven.lua index 3c2e1fbc51..624f1751f6 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_microwave_oven.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_microwave_oven.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/matter-appliance/src/test/test_oven.lua b/drivers/SmartThings/matter-appliance/src/test/test_oven.lua index 58c38e078c..76a23a0f44 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_oven.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_oven.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" test.set_rpc_version(6) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua b/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua index 3dc19750f3..0ac94856e3 100644 --- a/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua +++ b/drivers/SmartThings/matter-appliance/src/test/test_refrigerator.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" test.set_rpc_version(6) diff --git a/drivers/SmartThings/matter-energy/src/test/test_battery_storage.lua b/drivers/SmartThings/matter-energy/src/test/test_battery_storage.lua index e2c3cbd44c..5b5734acf5 100644 --- a/drivers/SmartThings/matter-energy/src/test/test_battery_storage.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_battery_storage.lua @@ -196,7 +196,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes .CumulativeEnergyImported:build_test_report_data(mock_device, BATTERY_STORAGE_EP, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 100000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --100Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 100000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) --100Wh test.socket.capability:__expect_send( mock_device:generate_test_message("importedEnergy", @@ -219,7 +219,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes .CumulativeEnergyExported:build_test_report_data(mock_device, BATTERY_STORAGE_EP, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 400000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --400Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 400000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) --400Wh test.socket.capability:__expect_send( mock_device:generate_test_message("exportedEnergy", @@ -245,7 +245,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes .CumulativeEnergyImported:build_test_report_data(mock_device, BATTERY_STORAGE_EP, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 200000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --200Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 200000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) --200Wh test.socket.capability:__expect_send( mock_device:generate_test_message("importedEnergy", @@ -266,7 +266,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes .CumulativeEnergyExported:build_test_report_data(mock_device, BATTERY_STORAGE_EP, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 400000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --400Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 400000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) --400Wh test.socket.capability:__expect_send( mock_device:generate_test_message("exportedEnergy", diff --git a/drivers/SmartThings/matter-energy/src/test/test_solar_power.lua b/drivers/SmartThings/matter-energy/src/test/test_solar_power.lua index 8c5e4c6ff8..c7446af44b 100644 --- a/drivers/SmartThings/matter-energy/src/test/test_solar_power.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_solar_power.lua @@ -133,7 +133,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes .CumulativeEnergyExported:build_test_report_data(mock_device, SOLAR_POWER_EP_ONE, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 100000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --100Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 100000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) --100Wh test.socket.capability:__expect_send( mock_device:generate_test_message("main", @@ -156,7 +156,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes .CumulativeEnergyExported:build_test_report_data(mock_device, SOLAR_POWER_EP_TWO, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 150000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --150Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 150000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) --150Wh test.socket.capability:__expect_send( mock_device:generate_test_message("main", @@ -178,7 +178,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes .CumulativeEnergyExported:build_test_report_data(mock_device, SOLAR_POWER_EP_ONE, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 100000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --100Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 100000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) --100Wh test.socket.capability:__expect_send( mock_device:generate_test_message("main", @@ -190,7 +190,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes .CumulativeEnergyImported:build_test_report_data(mock_device, SOLAR_POWER_EP_ONE, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 100000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --100Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 100000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) --100Wh end ) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 27722d9207..3efd33b364 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -15,6 +15,11 @@ matterManufacturer: vendorId: 0x115F productId: 0x2807 deviceProfileName: lock-battery + - id: "4447/10346" + deviceLabel: Aqara Smart Lock U200 US + vendorId: 0x115F + productId: 0x286A + deviceProfileName: lock-user-pin #Eufy - id: "5427/1" deviceLabel: eufy Smart Lock E31 @@ -51,6 +56,27 @@ matterManufacturer: vendorId: 0x1533 productId: 0x0012 deviceProfileName: lock-user-pin-battery + - id: "5427/22" + deviceLabel: eufy FamiLock E32 + vendorId: 0x1533 + productId: 0x0016 + deviceProfileName: lock-user-pin-battery + - id: 5427/20 + deviceLabel: eufy FamiLock E40 + vendorId: 0x1533 + productId: 0x0014 + deviceProfileName: lock-user-pin-battery + #Kwikset + - id: "5153/66" + deviceLabel: Kwikset Halo Select Plus + vendorId: 0x1421 + productId: 0x0042 + deviceProfileName: lock-user-pin-battery + - id: "5153/129" + deviceLabel: Kwikset Aura Reach + vendorId: 0x1421 + productId: 0x0081 + deviceProfileName: lock-user-pin-battery #Level - id: "4767/1" deviceLabel: Level Lock Plus (Matter) diff --git a/drivers/SmartThings/matter-lock/profiles/base-lock-batteryLevel.yml b/drivers/SmartThings/matter-lock/profiles/base-lock-batteryLevel.yml index 257a3c6e3f..093c741219 100755 --- a/drivers/SmartThings/matter-lock/profiles/base-lock-batteryLevel.yml +++ b/drivers/SmartThings/matter-lock/profiles/base-lock-batteryLevel.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: base-lock-batteryLevel components: - id: main diff --git a/drivers/SmartThings/matter-lock/profiles/base-lock-nobattery.yml b/drivers/SmartThings/matter-lock/profiles/base-lock-nobattery.yml index 769e955219..c223facfbf 100755 --- a/drivers/SmartThings/matter-lock/profiles/base-lock-nobattery.yml +++ b/drivers/SmartThings/matter-lock/profiles/base-lock-nobattery.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: base-lock-nobattery components: - id: main diff --git a/drivers/SmartThings/matter-lock/profiles/base-lock.yml b/drivers/SmartThings/matter-lock/profiles/base-lock.yml index 094aa523cc..7d69a5c8e4 100755 --- a/drivers/SmartThings/matter-lock/profiles/base-lock.yml +++ b/drivers/SmartThings/matter-lock/profiles/base-lock.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: base-lock components: - id: main diff --git a/drivers/SmartThings/matter-lock/profiles/lock-lockalarm-batteryLevel.yml b/drivers/SmartThings/matter-lock/profiles/lock-lockalarm-batteryLevel.yml index f946fd27cc..83b8c097f7 100755 --- a/drivers/SmartThings/matter-lock/profiles/lock-lockalarm-batteryLevel.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-lockalarm-batteryLevel.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: lock-lockalarm-batteryLevel components: - id: main diff --git a/drivers/SmartThings/matter-lock/profiles/lock-lockalarm-nobattery.yml b/drivers/SmartThings/matter-lock/profiles/lock-lockalarm-nobattery.yml index 96641392bf..135a1e1986 100644 --- a/drivers/SmartThings/matter-lock/profiles/lock-lockalarm-nobattery.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-lockalarm-nobattery.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: lock-lockalarm-nobattery components: - id: main diff --git a/drivers/SmartThings/matter-lock/profiles/lock-lockalarm.yml b/drivers/SmartThings/matter-lock/profiles/lock-lockalarm.yml index 007d01aa2f..ed0dd126e6 100755 --- a/drivers/SmartThings/matter-lock/profiles/lock-lockalarm.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-lockalarm.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: lock-lockalarm components: - id: main diff --git a/drivers/SmartThings/matter-lock/profiles/lock-nocodes-notamper-batteryLevel.yml b/drivers/SmartThings/matter-lock/profiles/lock-nocodes-notamper-batteryLevel.yml index f55fbe012c..abd55420fd 100644 --- a/drivers/SmartThings/matter-lock/profiles/lock-nocodes-notamper-batteryLevel.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-nocodes-notamper-batteryLevel.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: lock-nocodes-notamper-batteryLevel components: - id: main diff --git a/drivers/SmartThings/matter-lock/profiles/lock-nocodes-notamper.yml b/drivers/SmartThings/matter-lock/profiles/lock-nocodes-notamper.yml index 2371c84732..20dec32602 100644 --- a/drivers/SmartThings/matter-lock/profiles/lock-nocodes-notamper.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-nocodes-notamper.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: lock-nocodes-notamper components: - id: main diff --git a/drivers/SmartThings/matter-lock/profiles/lock-without-codes-batteryLevel.yml b/drivers/SmartThings/matter-lock/profiles/lock-without-codes-batteryLevel.yml index 15ce853cfd..2d577cd19b 100755 --- a/drivers/SmartThings/matter-lock/profiles/lock-without-codes-batteryLevel.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-without-codes-batteryLevel.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: lock-without-codes-batteryLevel components: - id: main diff --git a/drivers/SmartThings/matter-lock/profiles/lock-without-codes-nobattery.yml b/drivers/SmartThings/matter-lock/profiles/lock-without-codes-nobattery.yml index 8de4bb1a5c..059b65e9f9 100755 --- a/drivers/SmartThings/matter-lock/profiles/lock-without-codes-nobattery.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-without-codes-nobattery.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: lock-without-codes-nobattery components: - id: main diff --git a/drivers/SmartThings/matter-lock/profiles/lock-without-codes.yml b/drivers/SmartThings/matter-lock/profiles/lock-without-codes.yml index 772b742f20..86bc5e45d3 100755 --- a/drivers/SmartThings/matter-lock/profiles/lock-without-codes.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-without-codes.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: lock-without-codes components: - id: main diff --git a/drivers/SmartThings/matter-lock/profiles/nonfunctional-lock-batteryLevel.yml b/drivers/SmartThings/matter-lock/profiles/nonfunctional-lock-batteryLevel.yml index fc3371b31c..cf342d9548 100644 --- a/drivers/SmartThings/matter-lock/profiles/nonfunctional-lock-batteryLevel.yml +++ b/drivers/SmartThings/matter-lock/profiles/nonfunctional-lock-batteryLevel.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: nonfunctional-lock-batteryLevel components: - id: main diff --git a/drivers/SmartThings/matter-lock/profiles/nonfunctional-lock.yml b/drivers/SmartThings/matter-lock/profiles/nonfunctional-lock.yml index 1990cbc025..e662780d55 100644 --- a/drivers/SmartThings/matter-lock/profiles/nonfunctional-lock.yml +++ b/drivers/SmartThings/matter-lock/profiles/nonfunctional-lock.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: nonfunctional-lock components: - id: main diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua index cbab5a4db0..e47a883143 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -64,6 +64,8 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x115f, 0x2802}, -- AQARA, U200 {0x115f, 0x2801}, -- AQARA, U300 {0x115f, 0x2807}, -- AQARA, U200 Lite + {0x115f, 0x2804}, -- AQARA, U400 + {0x115f, 0x286A}, -- AQARA, U200 US {0x147F, 0x0001}, -- U-tec {0x147F, 0x0008}, -- Ultraloq, Bolt Smart Matter Door Lock {0x144F, 0x4002}, -- Yale, Linus Smart Lock L2 @@ -75,6 +77,8 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x1533, 0x0010}, -- eufy, FamiLock S3 {0x1533, 0x0011}, -- eufy, FamiLock E34 {0x1533, 0x0012}, -- eufy, FamiLock E35 + {0x1533, 0x0016}, -- eufy, FamiLock E32 + {0x1533, 0x0014}, -- eufy, FamiLock E40 {0x135D, 0x00B1}, -- Nuki, Smart Lock Pro {0x135D, 0x00B2}, -- Nuki, Smart Lock {0x135D, 0x00C1}, -- Nuki, Smart Lock @@ -82,7 +86,9 @@ local NEW_MATTER_LOCK_PRODUCTS = { {0x135D, 0x00B0}, -- Nuki, Smart Lock {0x15F2, 0x0001}, -- Viomi, AiSafety Smart Lock E100 {0x158B, 0x0001}, -- Deasino, DS-MT01 - {0x10E1, 0x2002} -- VDA + {0x10E1, 0x2002}, -- VDA + {0x1421, 0x0042}, -- Kwikset Halo Select Plus + {0x1421, 0x0081}, -- Kwikset Aura Reach } local battery_support = { diff --git a/drivers/SmartThings/matter-sensor/fingerprints.yml b/drivers/SmartThings/matter-sensor/fingerprints.yml index 9a463e7db6..4295c87a93 100644 --- a/drivers/SmartThings/matter-sensor/fingerprints.yml +++ b/drivers/SmartThings/matter-sensor/fingerprints.yml @@ -10,6 +10,11 @@ matterManufacturer: vendorId: 0x115F productId: 0x2003 deviceProfileName: motion-illuminance-battery + - id: "4447/8197" + deviceLabel: Presence Multi-Sensor FP300 + vendorId: 0x115F + productId: 0x2005 + deviceProfileName: presence-illuminance-temperature-humidity-battery #Bosch - id: 4617/12309 deviceLabel: "Door/window contact II [M]" @@ -74,6 +79,37 @@ matterManufacturer: vendorId: 0x120B productId: 0x1008 deviceProfileName: leak-battery + - id: "4619/4192" + deviceLabel: Smart Humidity&Temperature Sensor + vendorId: 0x120B + productId: 0x1060 + deviceProfileName: temperature-humidity-battery + # Ikea + - id: "4476/32773" + deviceLabel: TIMMERFLOTTE temp/hmd sensor + vendorId: 0x117C + productId: 0x8005 + deviceProfileName: temperature-humidity-battery + - id: "4476/32774" + deviceLabel: KLIPPBOK Matter water leak sensor smart + vendorId: 0x117C + productId: 0x8006 + deviceProfileName: leak-battery + - id: "4476/12288" + deviceLabel: MYGGSPRAY wrlss mtn sensor + vendorId: 0x117C + productId: 0x3000 + deviceProfileName: motion-illuminance-battery + - id: "4476/12289" + deviceLabel: ALPSTUGA Matter air quality sensor smart + vendorId: 0x117C + productId: 0x3001 + deviceProfileName: aqs-modular-temp-humidity + - id: "4476/32775" + deviceLabel: MYGGBETT Matter door/window sensor smart + vendorId: 0x117C + productId: 0x8007 + deviceProfileName: contact-battery # Legrand - id: "Legrand/Netatmo/Smart-2-in-1-Sensor" deviceLabel: Netatmo Smart 2-in-1 Sensor @@ -118,6 +154,11 @@ matterManufacturer: vendorId: 0x1345 productId: 0x4201 deviceProfileName: motion-illuminance + - id: "4933/16898" + deviceLabel: Smart Presence Sensor (Thread) + vendorId: 0x1345 + productId: 0x4202 + deviceProfileName: motion-illuminance-battery # Neo - id: "4991/1122" deviceLabel: Door Sensor @@ -174,6 +215,11 @@ matterManufacturer: vendorId: 0x1596 productId: 0x0001 deviceProfileName: smoke-battery + - id: "5526/2" + deviceLabel: MSC-1 + vendorId: 0x1596 + productId: 0x0002 + deviceProfileName: smoke-co-comeas-battery # Siterwell - id: "4736/847" deviceLabel: Siterwell Door Window Sensor @@ -277,6 +323,13 @@ matterGeneric: deviceTypes: - id: 0x0076 # Smoke CO Alarm deviceProfileName: smoke-co + - id: "matter/smoke-temp-humidity/sensor" + deviceLabel: Matter Smoke/Temp/Humidity Sensor + deviceTypes: + - id: 0x0076 # Smoke CO Alarm + - id: 0x0307 # Humidity Sensor + - id: 0x0302 # Temperature Sensor + deviceProfileName: smoke-temp-humidity-battery - id: "matter/rain/sensor" deviceLabel: Matter Rain Sensor deviceTypes: diff --git a/drivers/SmartThings/matter-sensor/profiles/matter-motion-battery-illuminance.yml b/drivers/SmartThings/matter-sensor/profiles/matter-motion-battery-illuminance.yml index b6c30d3421..3bac085178 100644 --- a/drivers/SmartThings/matter-sensor/profiles/matter-motion-battery-illuminance.yml +++ b/drivers/SmartThings/matter-sensor/profiles/matter-motion-battery-illuminance.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: matter-motion-battery-illuminance components: - id: main diff --git a/drivers/SmartThings/matter-sensor/profiles/matter-motion-battery.yml b/drivers/SmartThings/matter-sensor/profiles/matter-motion-battery.yml index 0e2b57c71e..66ea49c033 100644 --- a/drivers/SmartThings/matter-sensor/profiles/matter-motion-battery.yml +++ b/drivers/SmartThings/matter-sensor/profiles/matter-motion-battery.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: matter-motion-battery components: - id: main diff --git a/drivers/SmartThings/matter-sensor/profiles/matter-motion-batteryLevel-illuminance.yml b/drivers/SmartThings/matter-sensor/profiles/matter-motion-batteryLevel-illuminance.yml index 87c7fdf701..f9f97b3360 100644 --- a/drivers/SmartThings/matter-sensor/profiles/matter-motion-batteryLevel-illuminance.yml +++ b/drivers/SmartThings/matter-sensor/profiles/matter-motion-batteryLevel-illuminance.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: matter-motion-batteryLevel-illuminance components: - id: main diff --git a/drivers/SmartThings/matter-sensor/profiles/matter-motion-batteryLevel.yml b/drivers/SmartThings/matter-sensor/profiles/matter-motion-batteryLevel.yml index afc752557f..fa497ba042 100644 --- a/drivers/SmartThings/matter-sensor/profiles/matter-motion-batteryLevel.yml +++ b/drivers/SmartThings/matter-sensor/profiles/matter-motion-batteryLevel.yml @@ -1,3 +1,4 @@ +# Deprecated: do not use this profile for device fingerprinting. name: matter-motion-batteryLevel components: - id: main diff --git a/drivers/SmartThings/matter-sensor/profiles/smoke-temp-humidity-battery.yml b/drivers/SmartThings/matter-sensor/profiles/smoke-temp-humidity-battery.yml new file mode 100644 index 0000000000..cadbe9eb8a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/smoke-temp-humidity-battery.yml @@ -0,0 +1,25 @@ +name: smoke-temp-humidity-battery +components: +- id: main + capabilities: + - id: smokeDetector + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: temperatureMeasurement + version: 1 + - id: battery + version: 1 + - id: hardwareFault + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SmokeDetector +preferences: + - preferenceId: tempOffset + explicit: true + - preferenceId: humidityOffset + explicit: true \ No newline at end of file diff --git a/drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/init.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/init.lua deleted file mode 100644 index 682c6db041..0000000000 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/init.lua +++ /dev/null @@ -1,35 +0,0 @@ --- Copyright 2022 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. - --- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. - -local types_mt = {} -types_mt.__types_cache = {} -types_mt.__index = function(self, key) - if types_mt.__types_cache[key] == nil then - types_mt.__types_cache[key] = require("PressureMeasurement.types." .. key) - end - return types_mt.__types_cache[key] -end - ---- @class PressureMeasurementTypes ---- - ---- @field public Feature PressureMeasurement.types.Feature -local PressureMeasurementTypes = {} - -setmetatable(PressureMeasurementTypes, types_mt) - -return PressureMeasurementTypes - diff --git a/drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-sensor/src/air-quality-sensor/init.lua b/drivers/SmartThings/matter-sensor/src/air-quality-sensor/init.lua deleted file mode 100644 index 37a03d1580..0000000000 --- a/drivers/SmartThings/matter-sensor/src/air-quality-sensor/init.lua +++ /dev/null @@ -1,677 +0,0 @@ --- 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 utils = require "st.utils" -local embedded_cluster_utils = require "embedded-cluster-utils" - -local log = require "log" -local AIR_QUALITY_SENSOR_DEVICE_TYPE_ID = 0x002C - -local SUPPORTED_COMPONENT_CAPABILITIES = "__supported_component_capabilities" - - --- Include driver-side definitions when lua libs api version is < 10 -local version = require "version" -if version.api < 10 then - clusters.AirQuality = require "AirQuality" - clusters.CarbonMonoxideConcentrationMeasurement = require "CarbonMonoxideConcentrationMeasurement" - clusters.CarbonDioxideConcentrationMeasurement = require "CarbonDioxideConcentrationMeasurement" - clusters.FormaldehydeConcentrationMeasurement = require "FormaldehydeConcentrationMeasurement" - clusters.NitrogenDioxideConcentrationMeasurement = require "NitrogenDioxideConcentrationMeasurement" - clusters.OzoneConcentrationMeasurement = require "OzoneConcentrationMeasurement" - clusters.Pm1ConcentrationMeasurement = require "Pm1ConcentrationMeasurement" - clusters.Pm10ConcentrationMeasurement = require "Pm10ConcentrationMeasurement" - clusters.Pm25ConcentrationMeasurement = require "Pm25ConcentrationMeasurement" - clusters.RadonConcentrationMeasurement = require "RadonConcentrationMeasurement" - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "TotalVolatileOrganicCompoundsConcentrationMeasurement" -end - -local function is_matter_air_quality_sensor(opts, driver, device) - for _, ep in ipairs(device.endpoints) do - for _, dt in ipairs(ep.device_types) do - if dt.device_type_id == AIR_QUALITY_SENSOR_DEVICE_TYPE_ID then - return true - end - end - end - - return false - end - -local subscribed_attributes = { - [capabilities.airQualityHealthConcern.ID] = { - clusters.AirQuality.attributes.AirQuality - }, - [capabilities.temperatureMeasurement.ID] = { - clusters.TemperatureMeasurement.attributes.MeasuredValue - }, - [capabilities.relativeHumidityMeasurement.ID] = { - clusters.RelativeHumidityMeasurement.attributes.MeasuredValue - }, - [capabilities.carbonMonoxideMeasurement.ID] = { - clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue, - clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.carbonMonoxideHealthConcern.ID] = { - clusters.CarbonMonoxideConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.carbonDioxideMeasurement.ID] = { - clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasuredValue, - clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.carbonDioxideHealthConcern.ID] = { - clusters.CarbonDioxideConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.nitrogenDioxideMeasurement.ID] = { - clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasuredValue, - clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasurementUnit - }, - [capabilities.nitrogenDioxideHealthConcern.ID] = { - clusters.NitrogenDioxideConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.ozoneMeasurement.ID] = { - clusters.OzoneConcentrationMeasurement.attributes.MeasuredValue, - clusters.OzoneConcentrationMeasurement.attributes.MeasurementUnit - }, - [capabilities.ozoneHealthConcern.ID] = { - clusters.OzoneConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.formaldehydeMeasurement.ID] = { - clusters.FormaldehydeConcentrationMeasurement.attributes.MeasuredValue, - clusters.FormaldehydeConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.formaldehydeHealthConcern.ID] = { - clusters.FormaldehydeConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.veryFineDustSensor.ID] = { - clusters.Pm1ConcentrationMeasurement.attributes.MeasuredValue, - clusters.Pm1ConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.veryFineDustHealthConcern.ID] = { - clusters.Pm1ConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.fineDustHealthConcern.ID] = { - clusters.Pm25ConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.dustSensor.ID] = { - clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue, - clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit, - clusters.Pm10ConcentrationMeasurement.attributes.MeasuredValue, - clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.dustHealthConcern.ID] = { - clusters.Pm10ConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.radonMeasurement.ID] = { - clusters.RadonConcentrationMeasurement.attributes.MeasuredValue, - clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.radonHealthConcern.ID] = { - clusters.RadonConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.tvocMeasurement.ID] = { - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasuredValue, - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.tvocHealthConcern.ID] = { - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue - } -} - -local units_required = { - clusters.CarbonMonoxideConcentrationMeasurement, - clusters.CarbonDioxideConcentrationMeasurement, - clusters.NitrogenDioxideConcentrationMeasurement, - clusters.OzoneConcentrationMeasurement, - clusters.FormaldehydeConcentrationMeasurement, - clusters.Pm1ConcentrationMeasurement, - clusters.Pm25ConcentrationMeasurement, - clusters.Pm10ConcentrationMeasurement, - clusters.RadonConcentrationMeasurement, - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement -} - -local tbl_contains = function(t, val) - for _, v in pairs(t) do - if v == val then - return true - end - end - return false -end - -local supported_profiles = -{ - "aqs", - "aqs-temp-humidity-all-level-all-meas", - "aqs-temp-humidity-all-level", - "aqs-temp-humidity-all-meas", - "aqs-temp-humidity-co2-pm25-tvoc-meas", - "aqs-temp-humidity-co2-pm1-pm25-pm10-meas", - "aqs-temp-humidity-tvoc-level-pm25-meas", - "aqs-temp-humidity-tvoc-meas", -} - -local CONCENTRATION_MEASUREMENT_MAP = { - [capabilities.carbonMonoxideMeasurement] = {"-co", clusters.CarbonMonoxideConcentrationMeasurement, "N/A"}, - [capabilities.carbonMonoxideHealthConcern] = {"-co", clusters.CarbonMonoxideConcentrationMeasurement, capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues}, - [capabilities.carbonDioxideMeasurement] = {"-co2", clusters.CarbonDioxideConcentrationMeasurement, "N/A"}, - [capabilities.carbonDioxideHealthConcern] = {"-co2", clusters.CarbonDioxideConcentrationMeasurement, capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues}, - [capabilities.nitrogenDioxideMeasurement] = {"-no2", clusters.NitrogenDioxideConcentrationMeasurement, "N/A"}, - [capabilities.nitrogenDioxideHealthConcern] = {"-no2", clusters.NitrogenDioxideConcentrationMeasurement, capabilities.nitrogenDioxideHealthConcern.supportedNitrogenDioxideValues}, - [capabilities.ozoneMeasurement] = {"-ozone", clusters.OzoneConcentrationMeasurement, "N/A"}, - [capabilities.ozoneHealthConcern] = {"-ozone", clusters.OzoneConcentrationMeasurement, capabilities.ozoneHealthConcern.supportedOzoneValues}, - [capabilities.formaldehydeMeasurement] = {"-ch2o", clusters.FormaldehydeConcentrationMeasurement, "N/A"}, - [capabilities.formaldehydeHealthConcern] = {"-ch2o", clusters.FormaldehydeConcentrationMeasurement, capabilities.formaldehydeHealthConcern.supportedFormaldehydeValues}, - [capabilities.veryFineDustSensor] = {"-pm1", clusters.Pm1ConcentrationMeasurement, "N/A"}, - [capabilities.veryFineDustHealthConcern] = {"-pm1", clusters.Pm1ConcentrationMeasurement, capabilities.veryFineDustHealthConcern.supportedVeryFineDustValues}, - [capabilities.fineDustSensor] = {"-pm25", clusters.Pm25ConcentrationMeasurement, "N/A"}, - [capabilities.fineDustHealthConcern] = {"-pm25", clusters.Pm25ConcentrationMeasurement, capabilities.fineDustHealthConcern.supportedFineDustValues}, - [capabilities.dustSensor] = {"-pm10", clusters.Pm10ConcentrationMeasurement, "N/A"}, - [capabilities.dustHealthConcern] = {"-pm10", clusters.Pm10ConcentrationMeasurement, capabilities.dustHealthConcern.supportedDustValues}, - [capabilities.radonMeasurement] = {"-radon", clusters.RadonConcentrationMeasurement, "N/A"}, - [capabilities.radonHealthConcern] = {"-radon", clusters.RadonConcentrationMeasurement, capabilities.radonHealthConcern.supportedRadonValues}, - [capabilities.tvocMeasurement] = {"-tvoc", clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement, "N/A"}, - [capabilities.tvocHealthConcern] = {"-tvoc", clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement, capabilities.tvocHealthConcern.supportedTvocValues}, -} - - -local CONCENTRATION_MEASUREMENT_PROFILE_ORDERING = { - capabilities.carbonMonoxideMeasurement, - capabilities.carbonMonoxideHealthConcern, - capabilities.carbonDioxideMeasurement, - capabilities.carbonDioxideHealthConcern, - capabilities.nitrogenDioxideMeasurement, - capabilities.nitrogenDioxideHealthConcern, - capabilities.ozoneMeasurement, - capabilities.ozoneHealthConcern, - capabilities.formaldehydeMeasurement, - capabilities.formaldehydeHealthConcern, - capabilities.veryFineDustSensor, - capabilities.veryFineDustHealthConcern, - capabilities.fineDustSensor, - capabilities.fineDustHealthConcern, - capabilities.dustSensor, - capabilities.dustHealthConcern, - capabilities.radonMeasurement, - capabilities.radonHealthConcern, - capabilities.tvocMeasurement, - capabilities.tvocHealthConcern, -} - -local function set_supported_health_concern_values(device, setter_function, cluster, cluster_ep) - -- read_datatype_value works since all the healthConcern capabilities' datatypes are equivalent to the one in airQualityHealthConcern - local read_datatype_value = capabilities.airQualityHealthConcern.airQualityHealthConcern - local supported_values = {read_datatype_value.unknown.NAME, read_datatype_value.good.NAME, read_datatype_value.unhealthy.NAME} - if cluster == clusters.AirQuality then - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.FAIR }) > 0 then - table.insert(supported_values, 3, read_datatype_value.moderate.NAME) - end - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.MODERATE }) > 0 then - table.insert(supported_values, 4, read_datatype_value.slightlyUnhealthy.NAME) - end - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.VERY_POOR }) > 0 then - table.insert(supported_values, read_datatype_value.veryUnhealthy.NAME) - end - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.EXTREMELY_POOR }) > 0 then - table.insert(supported_values, read_datatype_value.hazardous.NAME) - end - else -- ConcentrationMeasurement clusters - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.MEDIUM_LEVEL }) > 0 then - table.insert(supported_values, 3, read_datatype_value.moderate.NAME) - end - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.CRITICAL_LEVEL }) > 0 then - table.insert(supported_values, read_datatype_value.hazardous.NAME) - end - end - device:emit_event_for_endpoint(cluster_ep, setter_function(supported_values, { visibility = { displayed = false }})) -end - -local function create_level_measurement_profile(device) - local meas_name, level_name = "", "" - for _, cap in ipairs(CONCENTRATION_MEASUREMENT_PROFILE_ORDERING) do - local cap_id = cap.ID - local cluster = CONCENTRATION_MEASUREMENT_MAP[cap][2] - -- capability describes either a HealthConcern or Measurement/Sensor - if (cap_id:match("HealthConcern$")) then - local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) - if #attr_eps > 0 then - level_name = level_name .. CONCENTRATION_MEASUREMENT_MAP[cap][1] - set_supported_health_concern_values(device, CONCENTRATION_MEASUREMENT_MAP[cap][3], cluster, attr_eps[1]) - end - elseif (cap_id:match("Measurement$") or cap_id:match("Sensor$")) then - local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.NUMERIC_MEASUREMENT }) - if #attr_eps > 0 then - meas_name = meas_name .. CONCENTRATION_MEASUREMENT_MAP[cap][1] - end - end - end - return meas_name, level_name -end - -local function supported_level_measurements(device) - local measurement_caps, level_caps = {}, {} - for _, cap in ipairs(CONCENTRATION_MEASUREMENT_PROFILE_ORDERING) do - local cap_id = cap.ID - local cluster = CONCENTRATION_MEASUREMENT_MAP[cap][2] - -- capability describes either a HealthConcern or Measurement/Sensor - if (cap_id:match("HealthConcern$")) then - local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) - if #attr_eps > 0 then - table.insert(level_caps, cap_id) - end - elseif (cap_id:match("Measurement$") or cap_id:match("Sensor$")) then - local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.NUMERIC_MEASUREMENT }) - if #attr_eps > 0 then - table.insert(measurement_caps, cap_id) - end - end - end - return measurement_caps, level_caps -end - -local function match_profile_switch(driver, device) - local temp_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureMeasurement.ID) - local humidity_eps = embedded_cluster_utils.get_endpoints(device, clusters.RelativeHumidityMeasurement.ID) - - local profile_name = "aqs" - local aq_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) - set_supported_health_concern_values(device, capabilities.airQualityHealthConcern.supportedAirQualityValues, clusters.AirQuality, aq_eps[1]) - - if #temp_eps > 0 then - profile_name = profile_name .. "-temp" - end - if #humidity_eps > 0 then - profile_name = profile_name .. "-humidity" - end - - local meas_name, level_name = create_level_measurement_profile(device) - - -- If all endpoints are supported, use '-all' in the profile name so that it - -- remains under the profile name character limit - if level_name == "-co-co2-no2-ozone-ch2o-pm1-pm25-pm10-radon-tvoc" then - level_name = "-all" - end - if level_name ~= "" then - profile_name = profile_name .. level_name .. "-level" - end - - -- If all endpoints are supported, use '-all' in the profile name so that it - -- remains under the profile name character limit - if meas_name == "-co-co2-no2-ozone-ch2o-pm1-pm25-pm10-radon-tvoc" then - meas_name = "-all" - end - if meas_name ~= "" then - profile_name = profile_name .. meas_name .. "-meas" - end - - if not tbl_contains(supported_profiles, profile_name) then - device.log.warn_with({hub_logs=true}, string.format("No matching profile for device. Tried to use profile %s", profile_name)) - - local function meas_find(sub_name) - return string.match(meas_name, sub_name) ~= nil - end - - -- try to best match to existing profiles - -- these checks, meas_find("co%-") and meas_find("co$"), match the string to co and NOT co2. - if meas_find("co%-") or meas_find("co$") or meas_find("no2") or meas_find("ozone") or meas_find("ch2o") or - meas_find("pm1") or meas_find("pm10") or meas_find("radon") then - profile_name = "aqs-temp-humidity-all-meas" - elseif #humidity_eps > 0 or #temp_eps > 0 or meas_find("co2") or meas_find("pm25") or meas_find("tvoc") then - profile_name = "aqs-temp-humidity-co2-pm25-tvoc-meas" - else - -- device only supports air quality at this point - profile_name = "aqs" - end - end - device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s", profile_name)) - device:try_update_metadata({profile = profile_name}) -end - -local function supports_capability_by_id_modular(device, capability, component) - if not device:get_field(SUPPORTED_COMPONENT_CAPABILITIES) then - device.log.warn_with({hub_logs = true}, "Device has overriden supports_capability_by_id, but does not have supported capabilities set.") - return false - end - for _, component_capabilities in ipairs(device:get_field(SUPPORTED_COMPONENT_CAPABILITIES)) do - local comp_id = component_capabilities[1] - local capability_ids = component_capabilities[2] - if (component == nil) or (component == comp_id) then - for _, cap in ipairs(capability_ids) do - if cap == capability then - return true - end - end - end - end - return false -end - -local function match_modular_profile(driver, device) - local temp_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureMeasurement.ID) - local humidity_eps = embedded_cluster_utils.get_endpoints(device, clusters.RelativeHumidityMeasurement.ID) - - local optional_supported_component_capabilities = {} - local main_component_capabilities = {} - local profile_name - local MAIN_COMPONENT_IDX = 1 - local CAPABILITIES_LIST_IDX = 2 - - if #temp_eps > 0 then - table.insert(main_component_capabilities, capabilities.temperatureMeasurement.ID) - end - if #humidity_eps > 0 then - table.insert(main_component_capabilities, capabilities.relativeHumidityMeasurement.ID) - end - - local measurement_caps, level_caps = supported_level_measurements(device) - - for _, cap_id in ipairs(measurement_caps) do - table.insert(main_component_capabilities, cap_id) - end - - for _, cap_id in ipairs(level_caps) do - table.insert(main_component_capabilities, cap_id) - end - - table.insert(optional_supported_component_capabilities, {"main", main_component_capabilities}) - - if #temp_eps > 0 and #humidity_eps > 0 then - profile_name = "aqs-modular-temp-humidity" - elseif #temp_eps > 0 then - profile_name = "aqs-modular-temp" - elseif #humidity_eps > 0 then - profile_name = "aqs-modular-humidity" - else - profile_name = "aqs-modular" - end - - device:try_update_metadata({profile = profile_name, optional_component_capabilities = optional_supported_component_capabilities}) - - -- earlier modular profile gating (min api v14, rpc 8) ensures we are running >= 0.57 FW. - -- This gating specifies a workaround required only for 0.57 FW, which is not needed for 0.58 and higher. - if version.api < 15 or version.rpc < 9 then - -- add mandatory capabilities for subscription - local total_supported_capabilities = optional_supported_component_capabilities - table.insert(total_supported_capabilities[MAIN_COMPONENT_IDX][CAPABILITIES_LIST_IDX], capabilities.airQualityHealthConcern.ID) - table.insert(total_supported_capabilities[MAIN_COMPONENT_IDX][CAPABILITIES_LIST_IDX], capabilities.refresh.ID) - table.insert(total_supported_capabilities[MAIN_COMPONENT_IDX][CAPABILITIES_LIST_IDX], capabilities.firmwareUpdate.ID) - - device:set_field(SUPPORTED_COMPONENT_CAPABILITIES, total_supported_capabilities, { persist = true }) - end -end - -local function do_configure(driver, device) - -- we have to read the unit before reports of values will do anything - for _, cluster in ipairs(units_required) do - device:send(cluster.attributes.MeasurementUnit:read(device)) - end - if version.api >= 14 and version.rpc >= 8 then - match_modular_profile(driver, device) - else - match_profile_switch(driver, device) - end -end - -local function driver_switched(driver, device) - -- we have to read the unit before reports of values will do anything - for _, cluster in ipairs(units_required) do - device:send(cluster.attributes.MeasurementUnit:read(device)) - end - if version.api >= 14 and version.rpc >= 8 then - match_modular_profile(driver, device) - else - match_profile_switch(driver, device) - end -end - -local function device_init(driver, device) - if device:get_field(SUPPORTED_COMPONENT_CAPABILITIES) and (version.api < 15 or version.rpc < 9) then - -- assume that device is using a modular profile on 0.57 FW, override supports_capability_by_id - -- library function to utilize optional capabilities - device:extend_device("supports_capability_by_id", supports_capability_by_id_modular) - end - device:subscribe() -end - -local function store_unit_factory(capability_name) - return function(driver, device, ib, response) - device:set_field(capability_name.."_unit", ib.data.value, {persist = true}) - end -end - -local units = { - PPM = 0, - PPB = 1, - PPT = 2, - MGM3 = 3, - UGM3 = 4, - NGM3 = 5, - PM3 = 6, - BQM3 = 7, - PCIL = 0xFF -- not in matter spec -} - -local unit_strings = { - [units.PPM] = "ppm", - [units.PPB] = "ppb", - [units.PPT] = "ppt", - [units.MGM3] = "mg/m^3", - [units.NGM3] = "ng/m^3", - [units.UGM3] = "μg/m^3", - [units.BQM3] = "Bq/m^3", - [units.PCIL] = "pCi/L" -} - -local unit_default = { - [capabilities.carbonMonoxideMeasurement.NAME] = units.PPM, - [capabilities.carbonDioxideMeasurement.NAME] = units.PPM, - [capabilities.nitrogenDioxideMeasurement.NAME] = units.PPM, - [capabilities.ozoneMeasurement.NAME] = units.PPM, - [capabilities.formaldehydeMeasurement.NAME] = units.PPM, - [capabilities.veryFineDustSensor.NAME] = units.UGM3, - [capabilities.fineDustSensor.NAME] = units.UGM3, - [capabilities.dustSensor.NAME] = units.UGM3, - [capabilities.radonMeasurement.NAME] = units.BQM3, - [capabilities.tvocMeasurement.NAME] = units.PPB -- TVOC is typically within the range of 0-5500 ppb, with good to moderate values being < 660 ppb -} - --- All ConcentrationMeasurement clusters inherit from the same base cluster definitions, --- so CarbonMonoxideConcentrationMeasurement is used below but the same enum types exist --- in all ConcentrationMeasurement clusters -local level_strings = { - [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.UNKNOWN] = "unknown", - [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.LOW] = "good", - [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.MEDIUM] = "moderate", - [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.HIGH] = "unhealthy", - [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.CRITICAL] = "hazardous", -} - -local conversion_tables = { - [units.PPM] = { - [units.PPM] = function(value) return utils.round(value) end, - [units.PPB] = function(value) return utils.round(value * (10^3)) end - }, - [units.PPB] = { - [units.PPM] = function(value) return utils.round(value/(10^3)) end, - [units.PPB] = function(value) return utils.round(value) end - }, - [units.PPT] = { - [units.PPM] = function(value) return utils.round(value/(10^6)) end - }, - [units.MGM3] = { - [units.UGM3] = function(value) return utils.round(value * (10^3)) end - }, - [units.UGM3] = { - [units.UGM3] = function(value) return utils.round(value) end - }, - [units.NGM3] = { - [units.UGM3] = function(value) return utils.round(value/(10^3)) end - }, - [units.BQM3] = { - [units.PCIL] = function(value) return utils.round(value/37) end - } -} - -local function unit_conversion(value, from_unit, to_unit) - local conversion_function = conversion_tables[from_unit][to_unit] - if conversion_function == nil then - log.info_with( {hub_logs = true} , string.format("Unsupported unit conversion from %s to %s", unit_strings[from_unit], unit_strings[to_unit])) - return 1 - end - - if value == nil then - log.info_with( {hub_logs = true} , "unit conversion value is nil") - return 1 - end - return conversion_function(value) -end - -local function measurementHandlerFactory(capability_name, attribute, target_unit) - return function(driver, device, ib, response) - local reporting_unit = device:get_field(capability_name.."_unit") - - if reporting_unit == nil then - reporting_unit = unit_default[capability_name] - device:set_field(capability_name.."_unit", reporting_unit, {persist = true}) - end - - if reporting_unit then - local value = unit_conversion(ib.data.value, reporting_unit, target_unit) - device:emit_event_for_endpoint(ib.endpoint_id, attribute({value = value, unit = unit_strings[target_unit]})) - - -- handle case where device profile supports both fineDustLevel and dustLevel - if capability_name == capabilities.fineDustSensor.NAME and device:supports_capability(capabilities.dustSensor) then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.dustSensor.fineDustLevel({value = value, unit = unit_strings[target_unit]})) - end - end - end -end - -local function levelHandlerFactory(attribute) - return function(driver, device, ib, response) - device:emit_event_for_endpoint(ib.endpoint_id, attribute(level_strings[ib.data.value])) - end -end - --- Matter Handlers -- -local function air_quality_attr_handler(driver, device, ib, response) - local state = ib.data.value - if state == 0 then -- Unknown - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.unknown()) - elseif state == 1 then -- Good - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.good()) - elseif state == 2 then -- Fair - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.moderate()) - elseif state == 3 then -- Moderate - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.slightlyUnhealthy()) - elseif state == 4 then -- Poor - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.unhealthy()) - elseif state == 5 then -- VeryPoor - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.veryUnhealthy()) - elseif state == 6 then -- ExtremelyPoor - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.hazardous()) - end -end - -local function pressure_attr_handler(driver, device, ib, response) - local pressure = utils.round(ib.data.value / 10.0) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.atmosphericPressureMeasurement.atmosphericPressure(pressure)) -end - -local function info_changed(driver, device, event, args) - if device.profile.id ~= args.old_st_store.profile.id then - if device:get_field(SUPPORTED_COMPONENT_CAPABILITIES) then - --re-up subscription with new capabilities using the modular supports_capability override - device:extend_device("supports_capability_by_id", supports_capability_by_id_modular) - end - device:subscribe() - end -end - -local matter_air_quality_sensor_handler = { - NAME = "matter-air-quality-sensor", - lifecycle_handlers = { - init = device_init, - doConfigure = do_configure, - infoChanged = info_changed, - driverSwitched = driver_switched - }, - matter_handlers = { - attr = { - [clusters.AirQuality.ID] = { - [clusters.AirQuality.attributes.AirQuality.ID] = air_quality_attr_handler, - }, - [clusters.PressureMeasurement.ID] = { - [clusters.PressureMeasurement.attributes.MeasuredValue.ID] = pressure_attr_handler - }, - [clusters.CarbonMonoxideConcentrationMeasurement.ID] = { - [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.carbonMonoxideMeasurement.NAME, capabilities.carbonMonoxideMeasurement.carbonMonoxideLevel, units.PPM), - [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.carbonMonoxideMeasurement.NAME), - [clusters.CarbonMonoxideConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.carbonMonoxideHealthConcern.carbonMonoxideHealthConcern), - }, - [clusters.CarbonDioxideConcentrationMeasurement.ID] = { - [clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.carbonDioxideMeasurement.NAME, capabilities.carbonDioxideMeasurement.carbonDioxide, units.PPM), - [clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.carbonDioxideMeasurement.NAME), - [clusters.CarbonDioxideConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern), - }, - [clusters.NitrogenDioxideConcentrationMeasurement.ID] = { - [clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.nitrogenDioxideMeasurement.NAME, capabilities.nitrogenDioxideMeasurement.nitrogenDioxide, units.PPM), - [clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.nitrogenDioxideMeasurement.NAME), - [clusters.NitrogenDioxideConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.nitrogenDioxideHealthConcern.nitrogenDioxideHealthConcern) - }, - [clusters.OzoneConcentrationMeasurement.ID] = { - [clusters.OzoneConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.ozoneMeasurement.NAME, capabilities.ozoneMeasurement.ozone, units.PPM), - [clusters.OzoneConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.ozoneMeasurement.NAME), - [clusters.OzoneConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.ozoneHealthConcern.ozoneHealthConcern) - }, - [clusters.FormaldehydeConcentrationMeasurement.ID] = { - [clusters.FormaldehydeConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.formaldehydeMeasurement.NAME, capabilities.formaldehydeMeasurement.formaldehydeLevel, units.PPM), - [clusters.FormaldehydeConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.formaldehydeMeasurement.NAME), - [clusters.FormaldehydeConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.formaldehydeHealthConcern.formaldehydeHealthConcern), - }, - [clusters.Pm1ConcentrationMeasurement.ID] = { - [clusters.Pm1ConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.veryFineDustSensor.NAME, capabilities.veryFineDustSensor.veryFineDustLevel, units.UGM3), - [clusters.Pm1ConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.veryFineDustSensor.NAME), - [clusters.Pm1ConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.veryFineDustHealthConcern.veryFineDustHealthConcern), - }, - [clusters.Pm25ConcentrationMeasurement.ID] = { - [clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.fineDustSensor.NAME, capabilities.fineDustSensor.fineDustLevel, units.UGM3), - [clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.fineDustSensor.NAME), - [clusters.Pm25ConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.fineDustHealthConcern.fineDustHealthConcern), - }, - [clusters.Pm10ConcentrationMeasurement.ID] = { - [clusters.Pm10ConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.dustSensor.NAME, capabilities.dustSensor.dustLevel, units.UGM3), - [clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.dustSensor.NAME), - [clusters.Pm10ConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.dustHealthConcern.dustHealthConcern), - }, - [clusters.RadonConcentrationMeasurement.ID] = { - [clusters.RadonConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.radonMeasurement.NAME, capabilities.radonMeasurement.radonLevel, units.PCIL), - [clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.radonMeasurement.NAME), - [clusters.RadonConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.radonHealthConcern.radonHealthConcern) - }, - [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.ID] = { - [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.tvocMeasurement.NAME, capabilities.tvocMeasurement.tvocLevel, units.PPB), - [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.tvocMeasurement.NAME), - [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.tvocHealthConcern.tvocHealthConcern) - } - } - }, - subscribed_attributes = subscribed_attributes, - can_handle = is_matter_air_quality_sensor -} - -return matter_air_quality_sensor_handler diff --git a/drivers/SmartThings/matter-sensor/src/AirQuality/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/init.lua similarity index 86% rename from drivers/SmartThings/matter-sensor/src/AirQuality/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/init.lua index 78e77fce51..4f6d19cc9f 100644 --- a/drivers/SmartThings/matter-sensor/src/AirQuality/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local AirQualityServerAttributes = require "AirQuality.server.attributes" -local AirQualityTypes = require "AirQuality.types" +local AirQualityServerAttributes = require "embedded_clusters.AirQuality.server.attributes" +local AirQualityTypes = require "embedded_clusters.AirQuality.types" local AirQuality = {} diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/AcceptedCommandList.lua similarity index 94% rename from drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/AcceptedCommandList.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/AcceptedCommandList.lua index 6a8d95df1d..a1e56c6597 100644 --- a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/AcceptedCommandList.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/AcceptedCommandList.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/AirQuality.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/AirQuality.lua similarity index 88% rename from drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/AirQuality.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/AirQuality.lua index f92f43543a..1beb6218f9 100644 --- a/drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/AirQuality.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/AirQuality.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local AirQuality = { ID = 0x0000, NAME = "AirQuality", - base_type = require "AirQuality.types.AirQualityEnum", + base_type = require "embedded_clusters.AirQuality.types.AirQualityEnum", } function AirQuality:new_value(...) diff --git a/drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/AttributeList.lua similarity index 94% rename from drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/AttributeList.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/AttributeList.lua index 93e96817e6..238b50ade3 100644 --- a/drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/AttributeList.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/AttributeList.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/EventList.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/EventList.lua similarity index 94% rename from drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/EventList.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/EventList.lua index 69155cd7ca..719f17a231 100644 --- a/drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/EventList.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/EventList.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/init.lua similarity index 75% rename from drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/init.lua index aef3b476a9..50295b081a 100644 --- a/drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("AirQuality.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.AirQuality.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/AirQuality/types/AirQualityEnum.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/types/AirQualityEnum.lua similarity index 94% rename from drivers/SmartThings/matter-thermostat/src/AirQuality/types/AirQualityEnum.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/types/AirQualityEnum.lua index 317a42dc9b..c2c255614a 100644 --- a/drivers/SmartThings/matter-thermostat/src/AirQuality/types/AirQualityEnum.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/types/AirQualityEnum.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-sensor/src/AirQuality/types/Feature.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/types/Feature.lua similarity index 96% rename from drivers/SmartThings/matter-sensor/src/AirQuality/types/Feature.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/types/Feature.lua index 86b90ce627..906a09a2bb 100644 --- a/drivers/SmartThings/matter-sensor/src/AirQuality/types/Feature.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/types/Feature.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-thermostat/src/AirQuality/types/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/types/init.lua similarity index 60% rename from drivers/SmartThings/matter-thermostat/src/AirQuality/types/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/types/init.lua index 88a2b861b7..b77d67de82 100644 --- a/drivers/SmartThings/matter-thermostat/src/AirQuality/types/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/AirQuality/types/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) if types_mt.__types_cache[key] == nil then - types_mt.__types_cache[key] = require("AirQuality.types." .. key) + types_mt.__types_cache[key] = require("embedded_clusters.AirQuality.types." .. key) end return types_mt.__types_cache[key] end diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/init.lua similarity index 92% rename from drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/init.lua index f5258619fa..c8e754419b 100644 --- a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/init.lua @@ -1,6 +1,6 @@ local cluster_base = require "st.matter.cluster_base" -local BooleanStateConfigurationServerAttributes = require "BooleanStateConfiguration.server.attributes" -local BooleanStateConfigurationTypes = require "BooleanStateConfiguration.types" +local BooleanStateConfigurationServerAttributes = require "embedded_clusters.BooleanStateConfiguration.server.attributes" +local BooleanStateConfigurationTypes = require "embedded_clusters.BooleanStateConfiguration.types" local BooleanStateConfiguration = {} diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/CurrentSensitivityLevel.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/CurrentSensitivityLevel.lua similarity index 95% rename from drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/CurrentSensitivityLevel.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/CurrentSensitivityLevel.lua index ea28550422..b2c9b67fbb 100644 --- a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/CurrentSensitivityLevel.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/CurrentSensitivityLevel.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/DefaultSensitivityLevel.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/DefaultSensitivityLevel.lua similarity index 94% rename from drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/DefaultSensitivityLevel.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/DefaultSensitivityLevel.lua index dc343735dc..760d7f397c 100644 --- a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/DefaultSensitivityLevel.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/DefaultSensitivityLevel.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/SensorFault.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/SensorFault.lua similarity index 87% rename from drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/SensorFault.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/SensorFault.lua index 83db4bc404..3786904abf 100644 --- a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/SensorFault.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/SensorFault.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local SensorFault = { ID = 0x0007, NAME = "SensorFault", - base_type = require "BooleanStateConfiguration.types.SensorFaultBitmap", + base_type = require "embedded_clusters.BooleanStateConfiguration.types.SensorFaultBitmap", } function SensorFault:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/SupportedSensitivityLevels.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/SupportedSensitivityLevels.lua similarity index 94% rename from drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/SupportedSensitivityLevels.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/SupportedSensitivityLevels.lua index c80177d043..12bff3e4a9 100644 --- a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/SupportedSensitivityLevels.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/SupportedSensitivityLevels.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/init.lua index 47ef55963b..daca1b7707 100644 --- a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("BooleanStateConfiguration.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.BooleanStateConfiguration.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/Feature.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/types/Feature.lua similarity index 97% rename from drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/Feature.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/types/Feature.lua index 3a4cb77058..dbc08cc0ed 100644 --- a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/Feature.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/types/Feature.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/SensorFaultBitmap.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/types/SensorFaultBitmap.lua similarity index 93% rename from drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/SensorFaultBitmap.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/types/SensorFaultBitmap.lua index c9399e50f1..9661734152 100644 --- a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/SensorFaultBitmap.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/types/SensorFaultBitmap.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/types/init.lua similarity index 62% rename from drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/types/init.lua index 79aae9c6e9..bc6835b4ac 100644 --- a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/BooleanStateConfiguration/types/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) if types_mt.__types_cache[key] == nil then - types_mt.__types_cache[key] = require("BooleanStateConfiguration.types." .. key) + types_mt.__types_cache[key] = require("embedded_clusters.BooleanStateConfiguration.types." .. key) end return types_mt.__types_cache[key] end diff --git a/drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/init.lua similarity index 88% rename from drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/init.lua index f5109f8943..4de97147e4 100644 --- a/drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local CarbonDioxideConcentrationMeasurementServerAttributes = require "CarbonDioxideConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local CarbonDioxideConcentrationMeasurementServerAttributes = require "embedded_clusters.CarbonDioxideConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local CarbonDioxideConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/LevelValue.lua similarity index 81% rename from drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/server/attributes/LevelValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/LevelValue.lua index 50127a3537..cc8f7b47eb 100644 --- a/drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/server/attributes/LevelValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/LevelValue.lua @@ -1,9 +1,12 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" local LevelValue = { ID = 0x000A, NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", } function LevelValue:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua similarity index 89% rename from drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasuredValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua index 763bbaa17b..ca0dafb0dc 100644 --- a/drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -1,7 +1,10 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" local MeasuredValue = { diff --git a/drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua similarity index 82% rename from drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua index d18f14b09f..9597906d3a 100644 --- a/drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -1,11 +1,14 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" local MeasurementUnit = { ID = 0x0008, NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", } function MeasurementUnit:new_value(...) diff --git a/drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/init.lua index 93583c2080..0206213e6f 100644 --- a/drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("CarbonDioxideConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.CarbonDioxideConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/init.lua similarity index 88% rename from drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/init.lua index e8cdb487f5..a6e1f24d1d 100644 --- a/drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local CarbonMonoxideConcentrationMeasurementServerAttributes = require "CarbonMonoxideConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local CarbonMonoxideConcentrationMeasurementServerAttributes = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local CarbonMonoxideConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/LevelValue.lua similarity index 81% rename from drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/server/attributes/LevelValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/LevelValue.lua index 50127a3537..cc8f7b47eb 100644 --- a/drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/server/attributes/LevelValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/LevelValue.lua @@ -1,9 +1,12 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" local LevelValue = { ID = 0x000A, NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", } function LevelValue:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasuredValue.lua similarity index 89% rename from drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasuredValue.lua index 763bbaa17b..ca0dafb0dc 100644 --- a/drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -1,7 +1,10 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" local MeasuredValue = { diff --git a/drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua similarity index 82% rename from drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua index d18f14b09f..9597906d3a 100644 --- a/drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -1,11 +1,14 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" local MeasurementUnit = { ID = 0x0008, NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", } function MeasurementUnit:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/init.lua index 2307e8977d..1a7e7b508c 100644 --- a/drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("CarbonMonoxideConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.CarbonMonoxideConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/init.lua similarity index 92% rename from drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/init.lua index 596d1bfe80..cb4cffa2d2 100644 --- a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local ConcentrationMeasurementServerAttributes = require "ConcentrationMeasurement.server.attributes" -local ConcentrationMeasurementTypes = require "ConcentrationMeasurement.types" +local ConcentrationMeasurementServerAttributes = require "embedded_clusters.ConcentrationMeasurement.server.attributes" +local ConcentrationMeasurementTypes = require "embedded_clusters.ConcentrationMeasurement.types" local ConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/server/attributes/LevelValue.lua similarity index 88% rename from drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/server/attributes/LevelValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/server/attributes/LevelValue.lua index e7023a336c..e4f88c8491 100644 --- a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/server/attributes/LevelValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/server/attributes/LevelValue.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local LevelValue = { ID = 0x000A, NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", } function LevelValue:new_value(...) diff --git a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/server/attributes/MeasuredValue.lua similarity index 93% rename from drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/server/attributes/MeasuredValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/server/attributes/MeasuredValue.lua index c658d2d3aa..2ab739841f 100644 --- a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/server/attributes/MeasurementUnit.lua similarity index 88% rename from drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/server/attributes/MeasurementUnit.lua index 3d50e8b97b..0fa14745c0 100644 --- a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local MeasurementUnit = { ID = 0x0008, NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", } function MeasurementUnit:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/server/attributes/init.lua index a1a0092151..19cde9aa55 100644 --- a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("ConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.ConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/types/Feature.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/types/Feature.lua similarity index 98% rename from drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/types/Feature.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/types/Feature.lua index 9aa2413903..0bb19bec62 100644 --- a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/types/Feature.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/types/Feature.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/types/LevelValueEnum.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/types/LevelValueEnum.lua similarity index 93% rename from drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/types/LevelValueEnum.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/types/LevelValueEnum.lua index b1264d72b8..02b4f727df 100644 --- a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/types/LevelValueEnum.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/types/LevelValueEnum.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/types/MeasurementUnitEnum.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/types/MeasurementUnitEnum.lua similarity index 94% rename from drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/types/MeasurementUnitEnum.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/types/MeasurementUnitEnum.lua index c8302c5cc4..6efd90901a 100644 --- a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/types/MeasurementUnitEnum.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/types/MeasurementUnitEnum.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/types/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/types/init.lua similarity index 62% rename from drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/types/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/types/init.lua index f6da1e6b62..c339f17414 100644 --- a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/types/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/ConcentrationMeasurement/types/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) if types_mt.__types_cache[key] == nil then - types_mt.__types_cache[key] = require("ConcentrationMeasurement.types." .. key) + types_mt.__types_cache[key] = require("embedded_clusters.ConcentrationMeasurement.types." .. key) end return types_mt.__types_cache[key] end diff --git a/drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/init.lua similarity index 88% rename from drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/init.lua index 5920a9dc66..cdfd1d597e 100644 --- a/drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local FormaldehydeConcentrationMeasurementServerAttributes = require "FormaldehydeConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local FormaldehydeConcentrationMeasurementServerAttributes = require "embedded_clusters.FormaldehydeConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local FormaldehydeConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua similarity index 81% rename from drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua index 50127a3537..cc8f7b47eb 100644 --- a/drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua @@ -1,9 +1,12 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" local LevelValue = { ID = 0x000A, NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", } function LevelValue:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua similarity index 89% rename from drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua index 763bbaa17b..ca0dafb0dc 100644 --- a/drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -1,7 +1,10 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" local MeasuredValue = { diff --git a/drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua similarity index 82% rename from drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua index d18f14b09f..9597906d3a 100644 --- a/drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -1,11 +1,14 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" local MeasurementUnit = { ID = 0x0008, NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", } function MeasurementUnit:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/init.lua index 37900b0fb1..3dee13abe3 100644 --- a/drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("FormaldehydeConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.FormaldehydeConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/init.lua similarity index 88% rename from drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/init.lua index b60e71050a..eae9be65f0 100644 --- a/drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local NitrogenDioxideConcentrationMeasurementServerAttributes = require "NitrogenDioxideConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local NitrogenDioxideConcentrationMeasurementServerAttributes = require "embedded_clusters.NitrogenDioxideConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local NitrogenDioxideConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/LevelValue.lua similarity index 81% rename from drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/server/attributes/LevelValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/LevelValue.lua index 50127a3537..cc8f7b47eb 100644 --- a/drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/server/attributes/LevelValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/LevelValue.lua @@ -1,9 +1,12 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" local LevelValue = { ID = 0x000A, NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", } function LevelValue:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua similarity index 89% rename from drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua index 763bbaa17b..ca0dafb0dc 100644 --- a/drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -1,7 +1,10 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" local MeasuredValue = { diff --git a/drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua similarity index 82% rename from drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua index d18f14b09f..9597906d3a 100644 --- a/drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -1,11 +1,14 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" local MeasurementUnit = { ID = 0x0008, NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", } function MeasurementUnit:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/init.lua index 06c3d6dd55..c82517d362 100644 --- a/drivers/SmartThings/matter-sensor/src/NitrogenDioxideConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("NitrogenDioxideConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.NitrogenDioxideConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/init.lua similarity index 85% rename from drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/init.lua index 83fd04857e..c49ea94b39 100644 --- a/drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local OzoneConcentrationMeasurementServerAttributes = require "OzoneConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local OzoneConcentrationMeasurementServerAttributes = require "embedded_clusters.OzoneConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local OzoneConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/init.lua index fe0048cd99..918b680495 100644 --- a/drivers/SmartThings/matter-sensor/src/OzoneConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("OzoneConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.OzoneConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/init.lua similarity index 85% rename from drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/init.lua index 98eebd407e..3b333b5417 100644 --- a/drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local Pm10ConcentrationMeasurementServerAttributes = require "Pm10ConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local Pm10ConcentrationMeasurementServerAttributes = require "embedded_clusters.Pm10ConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local Pm10ConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/init.lua index 55f08c7e43..3b1e6617a4 100644 --- a/drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("Pm10ConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.Pm10ConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/init.lua similarity index 85% rename from drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/init.lua index 0b3caa3bd2..b2e6656a9a 100644 --- a/drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local Pm1ConcentrationMeasurementServerAttributes = require "Pm1ConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local Pm1ConcentrationMeasurementServerAttributes = require "embedded_clusters.Pm1ConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local Pm1ConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/init.lua index f668e41a07..2635da32a6 100644 --- a/drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("Pm1ConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.Pm1ConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/init.lua similarity index 85% rename from drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/init.lua index 5234346d60..e6e6144f94 100644 --- a/drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local Pm25ConcentrationMeasurementServerAttributes = require "Pm25ConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local Pm25ConcentrationMeasurementServerAttributes = require "embedded_clusters.Pm25ConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local Pm25ConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/init.lua index 2c7d5fce7b..5c432da0ec 100644 --- a/drivers/SmartThings/matter-sensor/src/Pm25ConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("Pm25ConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.Pm25ConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/init.lua similarity index 82% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/init.lua index d47c239242..42b058eb02 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/init.lua @@ -1,23 +1,13 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. local cluster_base = require "st.matter.cluster_base" -local PressureMeasurementServerAttributes = require "PressureMeasurement.server.attributes" -local PressureMeasurementServerCommands = require "PressureMeasurement.server.commands" -local PressureMeasurementTypes = require "PressureMeasurement.types" +local PressureMeasurementServerAttributes = require "embedded_clusters.PressureMeasurement.server.attributes" +local PressureMeasurementServerCommands = require "embedded_clusters.PressureMeasurement.server.commands" +local PressureMeasurementTypes = require "embedded_clusters.PressureMeasurement.types" --- @class PressureMeasurement --- @alias PressureMeasurement @@ -141,4 +131,3 @@ setmetatable(PressureMeasurement.events, event_helper_mt) setmetatable(PressureMeasurement, {__index = cluster_base}) return PressureMeasurement - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/AcceptedCommandList.lua similarity index 85% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/AcceptedCommandList.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/AcceptedCommandList.lua index ee9fad2526..dc301eba35 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/AcceptedCommandList.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/AcceptedCommandList.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -123,4 +113,3 @@ end setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value, __index = AcceptedCommandList.base_type}) return AcceptedCommandList - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/AttributeList.lua similarity index 84% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/AttributeList.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/AttributeList.lua index d4d85b00a0..deba9d030f 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/AttributeList.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/AttributeList.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -123,4 +113,3 @@ end setmetatable(AttributeList, {__call = AttributeList.new_value, __index = AttributeList.base_type}) return AttributeList - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/EventList.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/EventList.lua similarity index 84% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/EventList.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/EventList.lua index b3dc7bf94c..f6040a17a0 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/EventList.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/EventList.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -123,4 +113,3 @@ end setmetatable(EventList, {__call = EventList.new_value, __index = EventList.base_type}) return EventList - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MaxMeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MaxMeasuredValue.lua similarity index 83% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MaxMeasuredValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MaxMeasuredValue.lua index 2484d99ef3..7bbf34d9e5 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MaxMeasuredValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MaxMeasuredValue.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -113,4 +103,3 @@ end setmetatable(MaxMeasuredValue, {__call = MaxMeasuredValue.new_value, __index = MaxMeasuredValue.base_type}) return MaxMeasuredValue - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MaxScaledValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MaxScaledValue.lua similarity index 83% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MaxScaledValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MaxScaledValue.lua index 56fe3132f9..91e5f4edc4 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MaxScaledValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MaxScaledValue.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -113,4 +103,3 @@ end setmetatable(MaxScaledValue, {__call = MaxScaledValue.new_value, __index = MaxScaledValue.base_type}) return MaxScaledValue - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MeasuredValue.lua similarity index 82% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MeasuredValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MeasuredValue.lua index 7d850f1a7c..b7c0484722 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MeasuredValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MeasuredValue.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -113,4 +103,3 @@ end setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) return MeasuredValue - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MinMeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MinMeasuredValue.lua similarity index 83% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MinMeasuredValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MinMeasuredValue.lua index 35ec62854f..ed4e421c77 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MinMeasuredValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MinMeasuredValue.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -113,4 +103,3 @@ end setmetatable(MinMeasuredValue, {__call = MinMeasuredValue.new_value, __index = MinMeasuredValue.base_type}) return MinMeasuredValue - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MinScaledValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MinScaledValue.lua similarity index 83% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MinScaledValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MinScaledValue.lua index 97bb0d44df..552ae6160a 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MinScaledValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/MinScaledValue.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -113,4 +103,3 @@ end setmetatable(MinScaledValue, {__call = MinScaledValue.new_value, __index = MinScaledValue.base_type}) return MinScaledValue - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/Scale.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/Scale.lua similarity index 82% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/Scale.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/Scale.lua index 5533e4e486..085153bd7a 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/Scale.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/Scale.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -113,4 +103,3 @@ end setmetatable(Scale, {__call = Scale.new_value, __index = Scale.base_type}) return Scale - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/ScaledTolerance.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/ScaledTolerance.lua similarity index 83% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/ScaledTolerance.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/ScaledTolerance.lua index d00d2a4195..4c14d72ea3 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/ScaledTolerance.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/ScaledTolerance.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -113,4 +103,3 @@ end setmetatable(ScaledTolerance, {__call = ScaledTolerance.new_value, __index = ScaledTolerance.base_type}) return ScaledTolerance - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/ScaledValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/ScaledValue.lua similarity index 82% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/ScaledValue.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/ScaledValue.lua index f2e513477b..28cfe27a7a 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/ScaledValue.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/ScaledValue.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -113,4 +103,3 @@ end setmetatable(ScaledValue, {__call = ScaledValue.new_value, __index = ScaledValue.base_type}) return ScaledValue - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/Tolerance.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/Tolerance.lua similarity index 82% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/Tolerance.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/Tolerance.lua index 55a39514a3..4d1b45ee48 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/Tolerance.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/Tolerance.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -113,4 +103,3 @@ end setmetatable(Tolerance, {__call = Tolerance.new_value, __index = Tolerance.base_type}) return Tolerance - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/init.lua similarity index 72% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/init.lua index 34c1959039..ebd4480609 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/attributes/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -18,7 +8,7 @@ local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("PressureMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.PressureMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) @@ -51,4 +41,3 @@ end setmetatable(PressureMeasurementServerAttributes, attr_mt) return PressureMeasurementServerAttributes - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/commands/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/commands/init.lua similarity index 53% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/commands/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/commands/init.lua index 3bb136bcb5..db5c3d9d45 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/commands/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/commands/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -18,7 +8,7 @@ local command_mt = {} command_mt.__command_cache = {} command_mt.__index = function(self, key) if command_mt.__command_cache[key] == nil then - local req_loc = string.format("PressureMeasurement.server.commands.%s", key) + local req_loc = string.format("embedded_clusters.PressureMeasurement.server.commands.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) @@ -38,4 +28,3 @@ end setmetatable(PressureMeasurementServerCommands, command_mt) return PressureMeasurementServerCommands - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/events/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/events/init.lua similarity index 51% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/events/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/events/init.lua index c6fec3b6b5..d96e0d7438 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/events/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/server/events/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -18,7 +8,7 @@ local event_mt = {} event_mt.__event_cache = {} event_mt.__index = function(self, key) if event_mt.__event_cache[key] == nil then - local req_loc = string.format("PressureMeasurement.server.events.%s", key) + local req_loc = string.format("embedded_clusters.PressureMeasurement.server.events.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) @@ -39,4 +29,3 @@ end setmetatable(PressureMeasurementEvents, event_mt) return PressureMeasurementEvents - diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/Feature.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/types/Feature.lua similarity index 73% rename from drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/Feature.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/types/Feature.lua index 5f5f070b7d..814c5ef226 100644 --- a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/Feature.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/types/Feature.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. @@ -78,4 +68,3 @@ end setmetatable(Feature, new_mt) return Feature - diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/types/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/types/init.lua new file mode 100644 index 0000000000..4f0c8b6bdc --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/PressureMeasurement/types/init.lua @@ -0,0 +1,24 @@ +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + types_mt.__types_cache[key] = require("embedded_clusters.PressureMeasurement.types." .. key) + end + return types_mt.__types_cache[key] +end + +--- @class PressureMeasurementTypes +--- + +--- @field public Feature PressureMeasurement.types.Feature +local PressureMeasurementTypes = {} + +setmetatable(PressureMeasurementTypes, types_mt) + +return PressureMeasurementTypes diff --git a/drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/init.lua similarity index 85% rename from drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/init.lua index 2a4cc04a0d..3d4b21d602 100644 --- a/drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local RadonConcentrationMeasurementServerAttributes = require "RadonConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local RadonConcentrationMeasurementServerAttributes = require "embedded_clusters.RadonConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local RadonConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/init.lua index b83ef67bfc..8aa225e510 100644 --- a/drivers/SmartThings/matter-sensor/src/RadonConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("RadonConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.RadonConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/init.lua similarity index 90% rename from drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/init.lua index 6246ac2ceb..b492c28f18 100644 --- a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/init.lua @@ -1,7 +1,10 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local SmokeCoAlarmServerAttributes = require "SmokeCoAlarm.server.attributes" -local SmokeCoAlarmServerCommands = require "SmokeCoAlarm.server.commands" -local SmokeCoAlarmTypes = require "SmokeCoAlarm.types" +local SmokeCoAlarmServerAttributes = require "embedded_clusters.SmokeCoAlarm.server.attributes" +local SmokeCoAlarmServerCommands = require "embedded_clusters.SmokeCoAlarm.server.commands" +local SmokeCoAlarmTypes = require "embedded_clusters.SmokeCoAlarm.types" local SmokeCoAlarm = {} diff --git a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/BatteryAlert.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/BatteryAlert.lua similarity index 89% rename from drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/BatteryAlert.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/BatteryAlert.lua index 9129921b7b..4ba713dcc4 100644 --- a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/BatteryAlert.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/BatteryAlert.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local BatteryAlert = { ID = 0x0003, NAME = "BatteryAlert", - base_type = require "SmokeCoAlarm.types.AlarmStateEnum", + base_type = require "embedded_clusters.SmokeCoAlarm.types.AlarmStateEnum", } function BatteryAlert:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/COState.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/COState.lua similarity index 88% rename from drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/COState.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/COState.lua index 0f99496b4f..db21d19922 100644 --- a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/COState.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/COState.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local COState = { ID = 0x0002, NAME = "COState", - base_type = require "SmokeCoAlarm.types.AlarmStateEnum", + base_type = require "embedded_clusters.SmokeCoAlarm.types.AlarmStateEnum", } function COState:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/HardwareFaultAlert.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/HardwareFaultAlert.lua similarity index 94% rename from drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/HardwareFaultAlert.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/HardwareFaultAlert.lua index 2a4a8ed949..2c7cfdde88 100644 --- a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/HardwareFaultAlert.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/HardwareFaultAlert.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/SmokeSensitivityLevel.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/SmokeSensitivityLevel.lua similarity index 91% rename from drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/SmokeSensitivityLevel.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/SmokeSensitivityLevel.lua index b193c3673e..e97da91b05 100644 --- a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/SmokeSensitivityLevel.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/SmokeSensitivityLevel.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local SmokeSensitivityLevel = { ID = 0x000B, NAME = "SmokeSensitivityLevel", - base_type = require "SmokeCoAlarm.types.SensitivityEnum", + base_type = require "embedded_clusters.SmokeCoAlarm.types.SensitivityEnum", } function SmokeSensitivityLevel:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/SmokeState.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/SmokeState.lua similarity index 88% rename from drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/SmokeState.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/SmokeState.lua index 08b04a9534..4879810c61 100644 --- a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/SmokeState.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/SmokeState.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local SmokeState = { ID = 0x0001, NAME = "SmokeState", - base_type = require "SmokeCoAlarm.types.AlarmStateEnum", + base_type = require "embedded_clusters.SmokeCoAlarm.types.AlarmStateEnum", } function SmokeState:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/TestInProgress.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/TestInProgress.lua similarity index 93% rename from drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/TestInProgress.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/TestInProgress.lua index d9ab8d2c25..42af52c87b 100644 --- a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/TestInProgress.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/TestInProgress.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/init.lua similarity index 75% rename from drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/init.lua index 6cc54ec5e5..f08cf54fd0 100644 --- a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("SmokeCoAlarm.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.SmokeCoAlarm.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/commands/SelfTestRequest.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/commands/SelfTestRequest.lua similarity index 96% rename from drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/commands/SelfTestRequest.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/commands/SelfTestRequest.lua index 49454a078c..e8d892d1c3 100644 --- a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/commands/SelfTestRequest.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/commands/SelfTestRequest.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/commands/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/commands/init.lua similarity index 76% rename from drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/commands/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/commands/init.lua index 6d768c069c..f1ff1ab72a 100644 --- a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/server/commands/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/server/commands/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local command_mt = {} command_mt.__command_cache = {} command_mt.__index = function(self, key) if command_mt.__command_cache[key] == nil then - local req_loc = string.format("SmokeCoAlarm.server.commands.%s", key) + local req_loc = string.format("embedded_clusters.SmokeCoAlarm.server.commands.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/types/AlarmStateEnum.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/types/AlarmStateEnum.lua similarity index 92% rename from drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/types/AlarmStateEnum.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/types/AlarmStateEnum.lua index 7352dbcd6e..ac20cac525 100644 --- a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/types/AlarmStateEnum.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/types/AlarmStateEnum.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/types/Feature.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/types/Feature.lua similarity index 95% rename from drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/types/Feature.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/types/Feature.lua index 22c7465a71..1918a3be00 100644 --- a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/types/Feature.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/types/Feature.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/types/SensitivityEnum.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/types/SensitivityEnum.lua similarity index 91% rename from drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/types/SensitivityEnum.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/types/SensitivityEnum.lua index 4ba098ccfa..5728278657 100644 --- a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/types/SensitivityEnum.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/types/SensitivityEnum.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/types/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/types/init.lua similarity index 60% rename from drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/types/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/types/init.lua index 8467396dc8..027aaaf344 100644 --- a/drivers/SmartThings/matter-sensor/src/SmokeCoAlarm/types/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/SmokeCoAlarm/types/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) if types_mt.__types_cache[key] == nil then - types_mt.__types_cache[key] = require("SmokeCoAlarm.types." .. key) + types_mt.__types_cache[key] = require("embedded_clusters.SmokeCoAlarm.types." .. key) end return types_mt.__types_cache[key] end diff --git a/drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/init.lua similarity index 89% rename from drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/init.lua index a99c1dea50..cad49a14e1 100644 --- a/drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local TotalVolatileOrganicCompoundsConcentrationMeasurementServerAttributes = require "TotalVolatileOrganicCompoundsConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local TotalVolatileOrganicCompoundsConcentrationMeasurementServerAttributes = require "embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local TotalVolatileOrganicCompoundsConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/init.lua index c0c1b65c37..b67cbcd7b6 100644 --- a/drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("TotalVolatileOrganicCompoundsConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/init.lua b/drivers/SmartThings/matter-sensor/src/init.lua index 5b9d88149e..73e34fcd56 100644 --- a/drivers/SmartThings/matter-sensor/src/init.lua +++ b/drivers/SmartThings/matter-sensor/src/init.lua @@ -1,222 +1,68 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" -local log = require "log" local clusters = require "st.matter.clusters" -local im = require "st.matter.interaction_model" local MatterDriver = require "st.matter.driver" -local utils = require "st.utils" -local embedded_cluster_utils = require "embedded-cluster-utils" +local version = require "version" + +local fields = require "sensor_utils.fields" +local device_cfg = require "sensor_utils.device_configuration" +local attribute_handlers = require "sensor_handlers.attribute_handlers" -- This can be removed once LuaLibs supports the PressureMeasurement cluster if not pcall(function(cluster) return clusters[cluster] end, "PressureMeasurement") then - clusters.PressureMeasurement = require "PressureMeasurement" + clusters.PressureMeasurement = require "embedded_clusters.PressureMeasurement" end -- Include driver-side definitions when lua libs api version is < 10 -local version = require "version" if version.api < 10 then - clusters.AirQuality = require "AirQuality" - clusters.CarbonMonoxideConcentrationMeasurement = require "CarbonMonoxideConcentrationMeasurement" - clusters.CarbonDioxideConcentrationMeasurement = require "CarbonDioxideConcentrationMeasurement" - clusters.FormaldehydeConcentrationMeasurement = require "FormaldehydeConcentrationMeasurement" - clusters.NitrogenDioxideConcentrationMeasurement = require "NitrogenDioxideConcentrationMeasurement" - clusters.OzoneConcentrationMeasurement = require "OzoneConcentrationMeasurement" - clusters.Pm1ConcentrationMeasurement = require "Pm1ConcentrationMeasurement" - clusters.Pm10ConcentrationMeasurement = require "Pm10ConcentrationMeasurement" - clusters.Pm25ConcentrationMeasurement = require "Pm25ConcentrationMeasurement" - clusters.RadonConcentrationMeasurement = require "RadonConcentrationMeasurement" - clusters.SmokeCoAlarm = require "SmokeCoAlarm" - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "TotalVolatileOrganicCompoundsConcentrationMeasurement" + clusters.AirQuality = require "embedded_clusters.AirQuality" + clusters.CarbonMonoxideConcentrationMeasurement = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement" + clusters.CarbonDioxideConcentrationMeasurement = require "embedded_clusters.CarbonDioxideConcentrationMeasurement" + clusters.FormaldehydeConcentrationMeasurement = require "embedded_clusters.FormaldehydeConcentrationMeasurement" + clusters.NitrogenDioxideConcentrationMeasurement = require "embedded_clusters.NitrogenDioxideConcentrationMeasurement" + clusters.OzoneConcentrationMeasurement = require "embedded_clusters.OzoneConcentrationMeasurement" + clusters.Pm1ConcentrationMeasurement = require "embedded_clusters.Pm1ConcentrationMeasurement" + clusters.Pm10ConcentrationMeasurement = require "embedded_clusters.Pm10ConcentrationMeasurement" + clusters.Pm25ConcentrationMeasurement = require "embedded_clusters.Pm25ConcentrationMeasurement" + clusters.RadonConcentrationMeasurement = require "embedded_clusters.RadonConcentrationMeasurement" + clusters.SmokeCoAlarm = require "embedded_clusters.SmokeCoAlarm" + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement" end -- Include driver-side definitions when lua libs api version is < 11 if version.api < 11 then - clusters.BooleanStateConfiguration = require "BooleanStateConfiguration" -end - -local TEMP_BOUND_RECEIVED = "__temp_bound_received" -local TEMP_MIN = "__temp_min" -local TEMP_MAX = "__temp_max" -local FLOW_BOUND_RECEIVED = "__flow_bound_received" -local FLOW_MIN = "__flow_min" -local FLOW_MAX = "__flow_max" - -local battery_support = { - NO_BATTERY = "NO_BATTERY", - BATTERY_LEVEL = "BATTERY_LEVEL", - BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE" -} - -local function get_field_for_endpoint(device, field, endpoint) - return device:get_field(string.format("%s_%d", field, endpoint)) -end - -local function set_field_for_endpoint(device, field, endpoint, value, additional_params) - device:set_field(string.format("%s_%d", field, endpoint), value, additional_params) -end - -local BOOLEAN_DEVICE_TYPE_INFO = { - ["RAIN_SENSOR"] = { id = 0x0044, sensitivity_preference = "rainSensitivity", sensitivity_max = "rainMax" }, - ["WATER_FREEZE_DETECTOR"] = { id = 0x0041, sensitivity_preference = "freezeSensitivity", sensitivity_max = "freezeMax" }, - ["WATER_LEAK_DETECTOR"] = { id = 0x0043, sensitivity_preference = "leakSensitivity", sensitivity_max = "leakMax" }, - ["CONTACT_SENSOR"] = { id = 0x0015, sensitivity_preference = "N/A", sensitivity_max = "N/A" }, -} - -local ORDERED_DEVICE_TYPE_INFO = { - "RAIN_SENSOR", - "WATER_FREEZE_DETECTOR", - "WATER_LEAK_DETECTOR", - "CONTACT_SENSOR" -} - -local function set_boolean_device_type_per_endpoint(driver, device) - for _, ep in ipairs(device.endpoints) do - for _, dt in ipairs(ep.device_types) do - for dt_name, info in pairs(BOOLEAN_DEVICE_TYPE_INFO) do - if dt.device_type_id == info.id then - device:set_field(dt_name, ep.endpoint_id, { persist = true }) - device:send(clusters.BooleanStateConfiguration.attributes.SupportedSensitivityLevels:read(device, ep.endpoint_id)) - end - end - end - end + clusters.BooleanStateConfiguration = require "embedded_clusters.BooleanStateConfiguration" end -local function supports_sensitivity_preferences(device) - local preference_names = "" - local sensitivity_eps = embedded_cluster_utils.get_endpoints(device, clusters.BooleanStateConfiguration.ID, - {feature_bitmap = clusters.BooleanStateConfiguration.types.Feature.SENSITIVITY_LEVEL}) - if sensitivity_eps and #sensitivity_eps > 0 then - for _, dt_name in ipairs(ORDERED_DEVICE_TYPE_INFO) do - for _, sensitivity_ep in pairs(sensitivity_eps) do - if device:get_field(dt_name) == sensitivity_ep and BOOLEAN_DEVICE_TYPE_INFO[dt_name].sensitivity_preference ~= "N/A" then - preference_names = preference_names .. "-" .. BOOLEAN_DEVICE_TYPE_INFO[dt_name].sensitivity_preference - end - end - end - end - return preference_names -end - -local function match_profile(driver, device, battery_supported) - local profile_name = "" - - 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 device:supports_capability(capabilities.atmosphericPressureMeasurement) then - profile_name = profile_name .. "-pressure" - end - - if device:supports_capability(capabilities.rainSensor) then - profile_name = profile_name .. "-rain" - end - - if device:supports_capability(capabilities.temperatureAlarm) then - profile_name = profile_name .. "-freeze" - end - - if device:supports_capability(capabilities.waterSensor) then - profile_name = profile_name .. "-leak" - end - - if device:supports_capability(capabilities.flowMeasurement) then - profile_name = profile_name .. "-flow" - end - - if device:supports_capability(capabilities.button) then - profile_name = profile_name .. "-button" - end - - if battery_supported == battery_support.BATTERY_PERCENTAGE then - profile_name = profile_name .. "-battery" - elseif battery_supported == battery_support.BATTERY_LEVEL then - profile_name = profile_name .. "-batteryLevel" - end - - if device:supports_capability(capabilities.hardwareFault) then - profile_name = profile_name .. "-fault" - end - - local concatenated_preferences = supports_sensitivity_preferences(device) - profile_name = profile_name .. concatenated_preferences - - if device:supports_capability(capabilities.motionSensor) then - local occupancy_support = "-motion" - -- If the Occupancy Sensing Cluster’s revision is >= 5 (corresponds to Lua Libs version 13+), and any of the AIR / RAD / RFS / VIS - -- features are supported by the device, use the presenceSensor capability. Otherwise, use the motionSensor capability. Currently, - -- presenceSensor only used for devices fingerprinting to the motion-illuminance-temperature-humidity-battery profile. - if profile_name == "-illuminance-temperature-humidity-battery" and version.api >= 13 then - if #device:get_endpoints(clusters.OccupancySensing.ID, {feature_bitmap = clusters.OccupancySensing.types.Feature.ACTIVE_INFRARED}) > 0 or - #device:get_endpoints(clusters.OccupancySensing.ID, {feature_bitmap = clusters.OccupancySensing.types.Feature.RADAR}) > 0 or - #device:get_endpoints(clusters.OccupancySensing.ID, {feature_bitmap = clusters.OccupancySensing.types.Feature.RF_SENSING}) > 0 or - #device:get_endpoints(clusters.OccupancySensing.ID, {feature_bitmap = clusters.OccupancySensing.types.Feature.VISION}) then - occupancy_support = "-presence" - end - end - profile_name = occupancy_support .. profile_name - end - - -- remove leading "-" - profile_name = string.sub(profile_name, 2) - - device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) - device:try_update_metadata({profile = profile_name}) -end +local SensorLifecycleHandlers = {} -local function do_configure(driver, device) +function SensorLifecycleHandlers.do_configure(driver, device) local battery_feature_eps = device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) if #battery_feature_eps > 0 then - local attribute_list_read = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) - attribute_list_read:merge(clusters.PowerSource.attributes.AttributeList:read()) - device:send(attribute_list_read) + device:send(clusters.PowerSource.attributes.AttributeList:read()) else - match_profile(driver, device, battery_support.NO_BATTERY) + device_cfg.match_profile(driver, device, fields.battery_support.NO_BATTERY) end end -local function device_init(driver, device) - log.info("device init") - set_boolean_device_type_per_endpoint(driver, device) +function SensorLifecycleHandlers.device_init(driver, device) + device.log.info("device init") + device_cfg.set_boolean_device_type_per_endpoint(driver, device) device:subscribe() end -local function info_changed(driver, device, event, args) +function SensorLifecycleHandlers.info_changed(driver, device, event, args) if device.profile.id ~= args.old_st_store.profile.id then - set_boolean_device_type_per_endpoint(driver, device) + device_cfg.set_boolean_device_type_per_endpoint(driver, device) device:subscribe() end if not device.preferences then return end - for dt_name, info in pairs(BOOLEAN_DEVICE_TYPE_INFO) do + for dt_name, info in pairs(fields.BOOLEAN_DEVICE_TYPE_INFO) do local dt_ep = device:get_field(dt_name) if dt_ep and info.sensitivity_preference and (device.preferences[info.sensitivity_preference] ~= args.old_st_store.preferences[info.sensitivity_preference]) then local sensitivity_preference = device.preferences[info.sensitivity_preference] @@ -234,244 +80,76 @@ local function info_changed(driver, device, event, args) 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)) -end - -local function temperature_attr_handler(driver, device, ib, response) - local measured_value = ib.data.value - if measured_value ~= nil then - local temp = measured_value / 100.0 - local unit = "C" - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.temperatureMeasurement.temperature({value = temp, unit = unit})) - end -end - -local temp_attr_handler_factory = function(minOrMax) - return function(driver, device, ib, response) - if ib.data.value == nil then - return - end - local temp = ib.data.value / 100.0 - local unit = "C" - set_field_for_endpoint(device, TEMP_BOUND_RECEIVED..minOrMax, ib.endpoint_id, temp) - local min = get_field_for_endpoint(device, TEMP_BOUND_RECEIVED..TEMP_MIN, ib.endpoint_id) - local max = get_field_for_endpoint(device, TEMP_BOUND_RECEIVED..TEMP_MAX, ib.endpoint_id) - if min ~= nil and max ~= nil then - if min < max then - -- Only emit the capability for RPC version >= 5 (unit conversion for - -- temperature range capability is only supported for RPC >= 5) - if version.rpc >= 5 then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = min, maximum = max }, unit = unit })) - end - set_field_for_endpoint(device, TEMP_BOUND_RECEIVED..TEMP_MIN, ib.endpoint_id, nil) - set_field_for_endpoint(device, TEMP_BOUND_RECEIVED..TEMP_MAX, ib.endpoint_id, nil) - else - device.log.warn_with({hub_logs = true}, string.format("Device reported a min temperature %d that is not lower than the reported max temperature %d", min, max)) - end - end - end -end - -local function humidity_attr_handler(driver, device, ib, response) - local measured_value = ib.data.value - if measured_value ~= nil then - local humidity = utils.round(measured_value / 100.0) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.relativeHumidityMeasurement.humidity(humidity)) - end -end - -local BOOLEAN_CAP_EVENT_MAP = { - [true] = { - ["WATER_FREEZE_DETECTOR"] = capabilities.temperatureAlarm.temperatureAlarm.freeze(), - ["WATER_LEAK_DETECTOR"] = capabilities.waterSensor.water.wet(), - ["RAIN_SENSOR"] = capabilities.rainSensor.rain.detected(), - ["CONTACT_SENSOR"] = capabilities.contactSensor.contact.closed(), - }, - [false] = { - ["WATER_FREEZE_DETECTOR"] = capabilities.temperatureAlarm.temperatureAlarm.cleared(), - ["WATER_LEAK_DETECTOR"] = capabilities.waterSensor.water.dry(), - ["RAIN_SENSOR"] = capabilities.rainSensor.rain.undetected(), - ["CONTACT_SENSOR"] = capabilities.contactSensor.contact.open(), - } -} - -local function boolean_attr_handler(driver, device, ib, response) - local name - for dt_name, _ in pairs(BOOLEAN_DEVICE_TYPE_INFO) do - local dt_ep_id = device:get_field(dt_name) - if ib.endpoint_id == dt_ep_id then - name = dt_name - break - end - end - if name then - device:emit_event_for_endpoint(ib.endpoint_id, BOOLEAN_CAP_EVENT_MAP[ib.data.value][name]) - elseif device:supports_capability(capabilities.contactSensor) then - -- The generic case where no device type has been specified but the profile uses this capability. - device:emit_event_for_endpoint(ib.endpoint_id, BOOLEAN_CAP_EVENT_MAP[ib.data.value]["CONTACT_SENSOR"]) - else - log.error("No Boolean device type found on an endpoint, BooleanState handler aborted") - end -end - -local function supported_sensitivities_handler(driver, device, ib, response) - if not ib.data.value then - return - end - - for dt_name, info in pairs(BOOLEAN_DEVICE_TYPE_INFO) do - if device:get_field(dt_name) == ib.endpoint_id then - device:set_field(info.sensitivity_max, ib.data.value, {persist = true}) - end - end -end - -local function sensor_fault_handler(driver, device, ib, response) - if ib.data.value > 0 then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.hardwareFault.hardwareFault.detected()) - else - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.hardwareFault.hardwareFault.clear()) - end -end - -local function battery_percent_remaining_attr_handler(driver, device, ib, response) - if ib.data.value then - device:emit_event(capabilities.battery.battery(math.floor(ib.data.value / 2.0 + 0.5))) - end -end - -local function battery_charge_level_attr_handler(driver, device, ib, response) - if ib.data.value == clusters.PowerSource.types.BatChargeLevelEnum.OK then - device:emit_event(capabilities.batteryLevel.battery.normal()) - elseif ib.data.value == clusters.PowerSource.types.BatChargeLevelEnum.WARNING then - device:emit_event(capabilities.batteryLevel.battery.warning()) - elseif ib.data.value == clusters.PowerSource.types.BatChargeLevelEnum.CRITICAL then - device:emit_event(capabilities.batteryLevel.battery.critical()) - end -end - -local function power_source_attribute_list_handler(driver, device, ib, response) - for _, attr in ipairs(ib.data.elements) do - -- Re-profile the device if BatPercentRemaining (Attribute ID 0x0C) or - -- BatChargeLevel (Attribute ID 0x0E) is present. - if attr.value == 0x0C then - match_profile(driver, device, battery_support.BATTERY_PERCENTAGE) - return - elseif attr.value == 0x0E then - match_profile(driver, device, battery_support.BATTERY_LEVEL) - return - end - end -end - -local function occupancy_attr_handler(driver, device, ib, response) - if device:supports_capability(capabilities.motionSensor) then - device:emit_event(ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) - else - device:emit_event(ib.data.value == 0x01 and capabilities.presenceSensor.presence("present") or capabilities.presenceSensor.presence("not present")) - end -end - -local function pressure_attr_handler(driver, device, ib, response) - local measured_value = ib.data.value - if measured_value ~= nil then - local kPa = utils.round(measured_value / 10.0) - local unit = "kPa" - device:emit_event(capabilities.atmosphericPressureMeasurement.atmosphericPressure({value = kPa, unit = unit})) - end -end - -local function flow_attr_handler(driver, device, ib, response) - local measured_value = ib.data.value - if measured_value ~= nil then - local flow = measured_value / 10.0 - local unit = "m^3/h" - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.flowMeasurement.flow({value = flow, unit = unit})) - end -end - -local flow_attr_handler_factory = function(minOrMax) - return function(driver, device, ib, response) - if ib.data.value == nil then - return - end - local flow_bound = ib.data.value / 10.0 - local unit = "m^3/h" - set_field_for_endpoint(device, FLOW_BOUND_RECEIVED..minOrMax, ib.endpoint_id, flow_bound) - local min = get_field_for_endpoint(device, FLOW_BOUND_RECEIVED..FLOW_MIN, ib.endpoint_id) - local max = get_field_for_endpoint(device, FLOW_BOUND_RECEIVED..FLOW_MAX, ib.endpoint_id) - if min ~= nil and max ~= nil then - if min < max then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.flowMeasurement.flowRange({ value = { minimum = min, maximum = max }, unit = unit })) - set_field_for_endpoint(device, FLOW_BOUND_RECEIVED..FLOW_MIN, ib.endpoint_id, nil) - set_field_for_endpoint(device, FLOW_BOUND_RECEIVED..FLOW_MAX, ib.endpoint_id, nil) - else - device.log.warn_with({hub_logs = true}, string.format("Device reported a min flow measurement %d that is not lower than the reported max flow measurement %d", min, max)) - end - end - end -end - local matter_driver_template = { lifecycle_handlers = { - init = device_init, - infoChanged = info_changed, - doConfigure = do_configure, + doConfigure = SensorLifecycleHandlers.do_configure, + init = SensorLifecycleHandlers.device_init, + infoChanged = SensorLifecycleHandlers.info_changed, }, matter_handlers = { attr = { - [clusters.RelativeHumidityMeasurement.ID] = { - [clusters.RelativeHumidityMeasurement.attributes.MeasuredValue.ID] = humidity_attr_handler + [clusters.BooleanState.ID] = { + [clusters.BooleanState.attributes.StateValue.ID] = attribute_handlers.boolean_state_value_handler }, - [clusters.TemperatureMeasurement.ID] = { - [clusters.TemperatureMeasurement.attributes.MeasuredValue.ID] = temperature_attr_handler, - [clusters.TemperatureMeasurement.attributes.MinMeasuredValue.ID] = temp_attr_handler_factory(TEMP_MIN), - [clusters.TemperatureMeasurement.attributes.MaxMeasuredValue.ID] = temp_attr_handler_factory(TEMP_MAX), + [clusters.BooleanStateConfiguration.ID] = { + [clusters.BooleanStateConfiguration.attributes.SensorFault.ID] = attribute_handlers.sensor_fault_handler, + [clusters.BooleanStateConfiguration.attributes.SupportedSensitivityLevels.ID] = attribute_handlers.supported_sensitivity_levels_handler, + }, + [clusters.FlowMeasurement.ID] = { + [clusters.FlowMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.flow_measured_value_handler, + [clusters.FlowMeasurement.attributes.MinMeasuredValue.ID] = attribute_handlers.flow_measured_value_bounds_factory(fields.FLOW_MIN), + [clusters.FlowMeasurement.attributes.MaxMeasuredValue.ID] = attribute_handlers.flow_measured_value_bounds_factory(fields.FLOW_MAX) }, [clusters.IlluminanceMeasurement.ID] = { - [clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID] = illuminance_attr_handler + [clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.illuminance_measured_value_handler }, - [clusters.BooleanState.ID] = { - [clusters.BooleanState.attributes.StateValue.ID] = boolean_attr_handler + [clusters.OccupancySensing.ID] = { + [clusters.OccupancySensing.attributes.Occupancy.ID] = attribute_handlers.occupancy_measured_value_handler, }, [clusters.PowerSource.ID] = { - [clusters.PowerSource.attributes.AttributeList.ID] = power_source_attribute_list_handler, - [clusters.PowerSource.attributes.BatChargeLevel.ID] = battery_charge_level_attr_handler, - [clusters.PowerSource.attributes.BatPercentRemaining.ID] = battery_percent_remaining_attr_handler, - }, - [clusters.OccupancySensing.ID] = { - [clusters.OccupancySensing.attributes.Occupancy.ID] = occupancy_attr_handler, + [clusters.PowerSource.attributes.AttributeList.ID] = attribute_handlers.power_source_attribute_list_handler, + [clusters.PowerSource.attributes.BatChargeLevel.ID] = attribute_handlers.bat_charge_level_handler, + [clusters.PowerSource.attributes.BatPercentRemaining.ID] = attribute_handlers.bat_percent_remaining_handler, }, [clusters.PressureMeasurement.ID] = { - [clusters.PressureMeasurement.attributes.MeasuredValue.ID] = pressure_attr_handler, + [clusters.PressureMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.pressure_measured_value_handler, }, - [clusters.BooleanStateConfiguration.ID] = { - [clusters.BooleanStateConfiguration.attributes.SensorFault.ID] = sensor_fault_handler, - [clusters.BooleanStateConfiguration.attributes.SupportedSensitivityLevels.ID] = supported_sensitivities_handler, + [clusters.RelativeHumidityMeasurement.ID] = { + [clusters.RelativeHumidityMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.humidity_measured_value_handler + }, + [clusters.TemperatureMeasurement.ID] = { + [clusters.TemperatureMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.temperature_measured_value_handler, + [clusters.TemperatureMeasurement.attributes.MinMeasuredValue.ID] = attribute_handlers.temperature_measured_value_bounds_factory(fields.TEMP_MIN), + [clusters.TemperatureMeasurement.attributes.MaxMeasuredValue.ID] = attribute_handlers.temperature_measured_value_bounds_factory(fields.TEMP_MAX), }, [clusters.Thermostat.ID] = { - [clusters.Thermostat.attributes.LocalTemperature.ID] = temperature_attr_handler + [clusters.Thermostat.attributes.LocalTemperature.ID] = attribute_handlers.temperature_measured_value_handler -- TemperatureMeasurement.MeasuredValue handler can support this attibute }, - [clusters.FlowMeasurement.ID] = { - [clusters.FlowMeasurement.attributes.MeasuredValue.ID] = flow_attr_handler, - [clusters.FlowMeasurement.attributes.MinMeasuredValue.ID] = flow_attr_handler_factory(FLOW_MIN), - [clusters.FlowMeasurement.attributes.MaxMeasuredValue.ID] = flow_attr_handler_factory(FLOW_MAX) - } } }, - -- TODO Once capabilities all have default handlers move this info there, and - -- use `supported_capabilities` subscribed_attributes = { - [capabilities.relativeHumidityMeasurement.ID] = { - clusters.RelativeHumidityMeasurement.attributes.MeasuredValue + [capabilities.battery.ID] = { + clusters.PowerSource.attributes.BatPercentRemaining }, - [capabilities.temperatureMeasurement.ID] = { - clusters.TemperatureMeasurement.attributes.MeasuredValue, - clusters.TemperatureMeasurement.attributes.MinMeasuredValue, - clusters.TemperatureMeasurement.attributes.MaxMeasuredValue, - clusters.Thermostat.attributes.LocalTemperature + [capabilities.batteryLevel.ID] = { + clusters.PowerSource.attributes.BatChargeLevel, + clusters.SmokeCoAlarm.attributes.BatteryAlert, + }, + [capabilities.contactSensor.ID] = { + clusters.BooleanState.attributes.StateValue + }, + [capabilities.flowMeasurement.ID] = { + clusters.FlowMeasurement.attributes.MeasuredValue, + clusters.FlowMeasurement.attributes.MinMeasuredValue, + clusters.FlowMeasurement.attributes.MaxMeasuredValue + }, + [capabilities.hardwareFault.ID] = { + clusters.BooleanStateConfiguration.attributes.SensorFault, + -- THESE ARE USED IN THE CASE OF THE SMOKE CO ALARM. + -- TODO: move this unique subscription logic into the subdriver + clusters.SmokeCoAlarm.attributes.HardwareFaultAlert, + clusters.SmokeCoAlarm.attributes.BatteryAlert, + clusters.PowerSource.attributes.BatChargeLevel, }, [capabilities.illuminanceMeasurement.ID] = { clusters.IlluminanceMeasurement.attributes.MeasuredValue @@ -482,119 +160,114 @@ local matter_driver_template = { [capabilities.presenceSensor.ID] = { clusters.OccupancySensing.attributes.Occupancy }, - [capabilities.contactSensor.ID] = { - clusters.BooleanState.attributes.StateValue + [capabilities.rainSensor.ID] = { + clusters.BooleanState.attributes.StateValue, }, - [capabilities.battery.ID] = { - clusters.PowerSource.attributes.BatPercentRemaining + [capabilities.relativeHumidityMeasurement.ID] = { + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue }, - [capabilities.batteryLevel.ID] = { - clusters.PowerSource.attributes.BatChargeLevel, - clusters.SmokeCoAlarm.attributes.BatteryAlert, + [capabilities.temperatureAlarm.ID] = { + clusters.BooleanState.attributes.StateValue, }, - [capabilities.atmosphericPressureMeasurement.ID] = { - clusters.PressureMeasurement.attributes.MeasuredValue + [capabilities.temperatureMeasurement.ID] = { + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue, + clusters.Thermostat.attributes.LocalTemperature }, + [capabilities.waterSensor.ID] = { + clusters.BooleanState.attributes.StateValue, + }, + -- AIR QUALITY SENSOR SPECIFIC CAPABILITIES -- [capabilities.airQualityHealthConcern.ID] = { clusters.AirQuality.attributes.AirQuality }, - [capabilities.carbonMonoxideMeasurement.ID] = { - clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue, - clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit, + [capabilities.atmosphericPressureMeasurement.ID] = { + clusters.PressureMeasurement.attributes.MeasuredValue }, - [capabilities.carbonMonoxideHealthConcern.ID] = { - clusters.CarbonMonoxideConcentrationMeasurement.attributes.LevelValue, + [capabilities.carbonDioxideHealthConcern.ID] = { + clusters.CarbonDioxideConcentrationMeasurement.attributes.LevelValue, }, [capabilities.carbonDioxideMeasurement.ID] = { clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasuredValue, clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit, }, - [capabilities.carbonDioxideHealthConcern.ID] = { - clusters.CarbonDioxideConcentrationMeasurement.attributes.LevelValue, + [capabilities.carbonMonoxideHealthConcern.ID] = { + clusters.CarbonMonoxideConcentrationMeasurement.attributes.LevelValue, }, - [capabilities.nitrogenDioxideMeasurement.ID] = { - clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasuredValue, - clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasurementUnit + [capabilities.carbonMonoxideMeasurement.ID] = { + clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue, + clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit, }, - [capabilities.nitrogenDioxideHealthConcern.ID] = { - clusters.NitrogenDioxideConcentrationMeasurement.attributes.LevelValue, + [capabilities.dustHealthConcern.ID] = { + clusters.Pm10ConcentrationMeasurement.attributes.LevelValue, }, - [capabilities.ozoneMeasurement.ID] = { - clusters.OzoneConcentrationMeasurement.attributes.MeasuredValue, - clusters.OzoneConcentrationMeasurement.attributes.MeasurementUnit + [capabilities.dustSensor.ID] = { + clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue, + clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit, + clusters.Pm10ConcentrationMeasurement.attributes.MeasuredValue, + clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit, }, - [capabilities.ozoneHealthConcern.ID] = { - clusters.OzoneConcentrationMeasurement.attributes.LevelValue, + [capabilities.fineDustHealthConcern.ID] = { + clusters.Pm25ConcentrationMeasurement.attributes.LevelValue, }, - [capabilities.formaldehydeMeasurement.ID] = { - clusters.FormaldehydeConcentrationMeasurement.attributes.MeasuredValue, - clusters.FormaldehydeConcentrationMeasurement.attributes.MeasurementUnit, + [capabilities.fineDustSensor.ID] = { + clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue, + clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit, }, [capabilities.formaldehydeHealthConcern.ID] = { clusters.FormaldehydeConcentrationMeasurement.attributes.LevelValue, }, - [capabilities.veryFineDustSensor.ID] = { - clusters.Pm1ConcentrationMeasurement.attributes.MeasuredValue, - clusters.Pm1ConcentrationMeasurement.attributes.MeasurementUnit, + [capabilities.formaldehydeMeasurement.ID] = { + clusters.FormaldehydeConcentrationMeasurement.attributes.MeasuredValue, + clusters.FormaldehydeConcentrationMeasurement.attributes.MeasurementUnit, }, - [capabilities.veryFineDustHealthConcern.ID] = { - clusters.Pm1ConcentrationMeasurement.attributes.LevelValue, + [capabilities.nitrogenDioxideHealthConcern.ID] = { + clusters.NitrogenDioxideConcentrationMeasurement.attributes.LevelValue, }, - [capabilities.fineDustHealthConcern.ID] = { - clusters.Pm25ConcentrationMeasurement.attributes.LevelValue, + [capabilities.nitrogenDioxideMeasurement.ID] = { + clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasuredValue, + clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasurementUnit }, - [capabilities.fineDustSensor.ID] = { - clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue, - clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit, + [capabilities.ozoneHealthConcern.ID] = { + clusters.OzoneConcentrationMeasurement.attributes.LevelValue, }, - [capabilities.dustSensor.ID] = { - clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue, - clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit, - clusters.Pm10ConcentrationMeasurement.attributes.MeasuredValue, - clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit, + [capabilities.ozoneMeasurement.ID] = { + clusters.OzoneConcentrationMeasurement.attributes.MeasuredValue, + clusters.OzoneConcentrationMeasurement.attributes.MeasurementUnit }, - [capabilities.dustHealthConcern.ID] = { - clusters.Pm10ConcentrationMeasurement.attributes.LevelValue, + [capabilities.radonHealthConcern.ID] = { + clusters.RadonConcentrationMeasurement.attributes.LevelValue, }, [capabilities.radonMeasurement.ID] = { clusters.RadonConcentrationMeasurement.attributes.MeasuredValue, clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit, }, - [capabilities.radonHealthConcern.ID] = { - clusters.RadonConcentrationMeasurement.attributes.LevelValue, + [capabilities.relativeHumidityMeasurement.ID] = { + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue + }, + [capabilities.tvocHealthConcern.ID] = { + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue }, [capabilities.tvocMeasurement.ID] = { clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasuredValue, clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit, }, - [capabilities.tvocHealthConcern.ID] = { - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue, + [capabilities.veryFineDustHealthConcern.ID] = { + clusters.Pm1ConcentrationMeasurement.attributes.LevelValue, }, - [capabilities.smokeDetector.ID] = { - clusters.SmokeCoAlarm.attributes.SmokeState, - clusters.SmokeCoAlarm.attributes.TestInProgress, + [capabilities.veryFineDustSensor.ID] = { + clusters.Pm1ConcentrationMeasurement.attributes.MeasuredValue, + clusters.Pm1ConcentrationMeasurement.attributes.MeasurementUnit, }, + -- SMOKE CO ALARM SPECIFIC CAPABILITIES -- [capabilities.carbonMonoxideDetector.ID] = { clusters.SmokeCoAlarm.attributes.COState, clusters.SmokeCoAlarm.attributes.TestInProgress, }, - [capabilities.hardwareFault.ID] = { - clusters.SmokeCoAlarm.attributes.HardwareFaultAlert, - clusters.BooleanStateConfiguration.attributes.SensorFault, - }, - [capabilities.waterSensor.ID] = { - clusters.BooleanState.attributes.StateValue, - }, - [capabilities.temperatureAlarm.ID] = { - clusters.BooleanState.attributes.StateValue, - }, - [capabilities.rainSensor.ID] = { - clusters.BooleanState.attributes.StateValue, - }, - [capabilities.flowMeasurement.ID] = { - clusters.FlowMeasurement.attributes.MeasuredValue, - clusters.FlowMeasurement.attributes.MinMeasuredValue, - clusters.FlowMeasurement.attributes.MaxMeasuredValue + [capabilities.smokeDetector.ID] = { + clusters.SmokeCoAlarm.attributes.SmokeState, + clusters.SmokeCoAlarm.attributes.TestInProgress, }, }, subscribed_events = { @@ -604,8 +277,7 @@ local matter_driver_template = { clusters.Switch.events.MultiPressComplete, } }, - capability_handlers = { - }, + capability_handlers = {}, supported_capabilities = { capabilities.temperatureMeasurement, capabilities.contactSensor, @@ -623,11 +295,7 @@ local matter_driver_template = { capabilities.hardwareFault, capabilities.flowMeasurement, }, - sub_drivers = { - require("air-quality-sensor"), - require("smoke-co-alarm"), - require("bosch-button-contact") - } + sub_drivers = require("sub_drivers"), } local matter_driver = MatterDriver("matter-sensor", matter_driver_template) diff --git a/drivers/SmartThings/matter-sensor/src/lazy_load_subdriver.lua b/drivers/SmartThings/matter-sensor/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..a04740d267 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/lazy_load_subdriver.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(sub_driver_name) + local MatterDriver = require "st.matter.driver" + local version = require "version" + if version.api >= 16 then + return MatterDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return MatterDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end +end diff --git a/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua new file mode 100644 index 0000000000..9bba480855 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua @@ -0,0 +1,204 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local st_utils = require "st.utils" +local sensor_utils = require "sensor_utils.utils" +local fields = require "sensor_utils.fields" +local device_cfg = require "sensor_utils.device_configuration" +local version = require "version" + +local AttributeHandlers = {} + + +-- [[ ILLUMINANCE CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.illuminance_measured_value_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)) +end + + +-- [[ TEMPERATURE MEASUREMENT CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.temperature_measured_value_handler(driver, device, ib, response) + local measured_value = ib.data.value + if measured_value ~= nil then + local temp = measured_value / 100.0 + local unit = "C" + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.temperatureMeasurement.temperature({value = temp, unit = unit})) + end +end + +function AttributeHandlers.temperature_measured_value_bounds_factory(minOrMax) + return function(driver, device, ib, response) + if ib.data.value == nil then + return + end + local temp = ib.data.value / 100.0 + local unit = "C" + sensor_utils.set_field_for_endpoint(device, fields.TEMP_BOUND_RECEIVED..minOrMax, ib.endpoint_id, temp) + local min = sensor_utils.get_field_for_endpoint(device, fields.TEMP_BOUND_RECEIVED..fields.TEMP_MIN, ib.endpoint_id) + local max = sensor_utils.get_field_for_endpoint(device, fields.TEMP_BOUND_RECEIVED..fields.TEMP_MAX, ib.endpoint_id) + if min ~= nil and max ~= nil then + if min < max then + -- Only emit the capability for RPC version >= 5 (unit conversion for + -- temperature range capability is only supported for RPC >= 5) + if version.rpc >= 5 then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = min, maximum = max }, unit = unit })) + end + sensor_utils.set_field_for_endpoint(device, fields.TEMP_BOUND_RECEIVED..fields.TEMP_MIN, ib.endpoint_id, nil) + sensor_utils.set_field_for_endpoint(device, fields.TEMP_BOUND_RECEIVED..fields.TEMP_MAX, ib.endpoint_id, nil) + else + device.log.warn_with({hub_logs = true}, string.format("Device reported a min temperature %d that is not lower than the reported max temperature %d", min, max)) + end + end + end +end + + +-- [[ RELATIVE HUMIDITY MEASUREMENT CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.humidity_measured_value_handler(driver, device, ib, response) + local measured_value = ib.data.value + if measured_value ~= nil then + local humidity = st_utils.round(measured_value / 100.0) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.relativeHumidityMeasurement.humidity(humidity)) + end +end + + +-- [[ BOOLEAN STATE CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.boolean_state_value_handler(driver, device, ib, response) + local name + for dt_name, _ in pairs(fields.BOOLEAN_DEVICE_TYPE_INFO) do + local dt_ep_id = device:get_field(dt_name) + if ib.endpoint_id == dt_ep_id then + name = dt_name + break + end + end + if name then + device:emit_event_for_endpoint(ib.endpoint_id, fields.BOOLEAN_CAP_EVENT_MAP[ib.data.value][name]) + elseif device:supports_capability(capabilities.contactSensor) then + -- The generic case where no device type has been specified but the profile uses this capability. + device:emit_event_for_endpoint(ib.endpoint_id, fields.BOOLEAN_CAP_EVENT_MAP[ib.data.value]["CONTACT_SENSOR"]) + else + device.log.error("No Boolean device type found on an endpoint, BooleanState handler aborted") + end +end + + +-- [[ BOOLEAN STATE CONFIGURATION CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.sensor_fault_handler(driver, device, ib, response) + if ib.data.value > 0 then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.hardwareFault.hardwareFault.detected()) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.hardwareFault.hardwareFault.clear()) + end +end + +function AttributeHandlers.supported_sensitivity_levels_handler(driver, device, ib, response) + if ib.data.value then + for dt_name, info in pairs(fields.BOOLEAN_DEVICE_TYPE_INFO) do + if device:get_field(dt_name) == ib.endpoint_id then + device:set_field(info.sensitivity_max, ib.data.value, {persist = true}) + end + end + end +end + + +-- [[ POWER SOURCE CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.bat_percent_remaining_handler(driver, device, ib, response) + if ib.data.value then + device:emit_event(capabilities.battery.battery(math.floor(ib.data.value / 2.0 + 0.5))) + end +end + +function AttributeHandlers.bat_charge_level_handler(driver, device, ib, response) + if ib.data.value == clusters.PowerSource.types.BatChargeLevelEnum.OK then + device:emit_event(capabilities.batteryLevel.battery.normal()) + elseif ib.data.value == clusters.PowerSource.types.BatChargeLevelEnum.WARNING then + device:emit_event(capabilities.batteryLevel.battery.warning()) + elseif ib.data.value == clusters.PowerSource.types.BatChargeLevelEnum.CRITICAL then + device:emit_event(capabilities.batteryLevel.battery.critical()) + end +end + +function AttributeHandlers.power_source_attribute_list_handler(driver, device, ib, response) + for _, attr in ipairs(ib.data.elements) do + -- Re-profile the device if BatPercentRemaining (Attribute ID 0x0C) or + -- BatChargeLevel (Attribute ID 0x0E) is present. + if attr.value == 0x0C then + device_cfg.match_profile(driver, device, fields.battery_support.BATTERY_PERCENTAGE) + return + elseif attr.value == 0x0E then + device_cfg.match_profile(driver, device, fields.battery_support.BATTERY_LEVEL) + return + end + end +end + + +-- [[ OCCUPANCY SENSING CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.occupancy_measured_value_handler(driver, device, ib, response) + if device:supports_capability(capabilities.motionSensor) then + device:emit_event(ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) + else + device:emit_event(ib.data.value == 0x01 and capabilities.presenceSensor.presence("present") or capabilities.presenceSensor.presence("not present")) + end +end + + +-- [[ PRESSURE MEASUREMENT CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.pressure_measured_value_handler(driver, device, ib, response) + local measured_value = ib.data.value + if measured_value ~= nil then + local kPa = st_utils.round(measured_value / 10.0) + local unit = "kPa" + device:emit_event(capabilities.atmosphericPressureMeasurement.atmosphericPressure({value = kPa, unit = unit})) + end +end + + +-- [[ FLOW MEASUREMENT CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.flow_measured_value_handler(driver, device, ib, response) + local measured_value = ib.data.value + if measured_value ~= nil then + local flow = measured_value / 10.0 + local unit = "m^3/h" + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.flowMeasurement.flow({value = flow, unit = unit})) + end +end + +function AttributeHandlers.flow_measured_value_bounds_factory(minOrMax) + return function(driver, device, ib, response) + if ib.data.value == nil then + return + end + local flow_bound = ib.data.value / 10.0 + local unit = "m^3/h" + sensor_utils.set_field_for_endpoint(device, fields.FLOW_BOUND_RECEIVED..minOrMax, ib.endpoint_id, flow_bound) + local min = sensor_utils.get_field_for_endpoint(device, fields.FLOW_BOUND_RECEIVED..fields.FLOW_MIN, ib.endpoint_id) + local max = sensor_utils.get_field_for_endpoint(device, fields.FLOW_BOUND_RECEIVED..fields.FLOW_MAX, ib.endpoint_id) + if min ~= nil and max ~= nil then + if min < max then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.flowMeasurement.flowRange({ value = { minimum = min, maximum = max }, unit = unit })) + sensor_utils.set_field_for_endpoint(device, fields.FLOW_BOUND_RECEIVED..fields.FLOW_MIN, ib.endpoint_id, nil) + sensor_utils.set_field_for_endpoint(device, fields.FLOW_BOUND_RECEIVED..fields.FLOW_MAX, ib.endpoint_id, nil) + else + device.log.warn_with({hub_logs = true}, string.format("Device reported a min flow measurement %d that is not lower than the reported max flow measurement %d", min, max)) + end + end + end +end + +return AttributeHandlers \ No newline at end of file diff --git a/drivers/SmartThings/matter-sensor/src/sensor_utils/device_configuration.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/device_configuration.lua new file mode 100644 index 0000000000..2c5f38524a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/device_configuration.lua @@ -0,0 +1,124 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local embedded_cluster_utils = require "sensor_utils.embedded_cluster_utils" +local fields = require "sensor_utils.fields" +local version = require "version" + +if version.api < 11 then + clusters.BooleanStateConfiguration = require "embedded_clusters.BooleanStateConfiguration" +end + +local DeviceConfiguration = {} + +function DeviceConfiguration.set_boolean_device_type_per_endpoint(driver, device) + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + for dt_name, info in pairs(fields.BOOLEAN_DEVICE_TYPE_INFO) do + if dt.device_type_id == info.id then + device:set_field(dt_name, ep.endpoint_id, { persist = true }) + device:send(clusters.BooleanStateConfiguration.attributes.SupportedSensitivityLevels:read(device, ep.endpoint_id)) + end + end + end + end +end + +local function supports_sensitivity_preferences(device) + local preference_names = "" + local sensitivity_eps = embedded_cluster_utils.get_endpoints(device, clusters.BooleanStateConfiguration.ID, + {feature_bitmap = clusters.BooleanStateConfiguration.types.Feature.SENSITIVITY_LEVEL}) + if sensitivity_eps and #sensitivity_eps > 0 then + for _, dt_name in ipairs(fields.ORDERED_DEVICE_TYPE_INFO) do + for _, sensitivity_ep in pairs(sensitivity_eps) do + if device:get_field(dt_name) == sensitivity_ep and fields.BOOLEAN_DEVICE_TYPE_INFO[dt_name].sensitivity_preference ~= "N/A" then + preference_names = preference_names .. "-" .. fields.BOOLEAN_DEVICE_TYPE_INFO[dt_name].sensitivity_preference + end + end + end + end + return preference_names +end + +function DeviceConfiguration.match_profile(driver, device, battery_supported) + local profile_name = "" + + 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 device:supports_capability(capabilities.atmosphericPressureMeasurement) then + profile_name = profile_name .. "-pressure" + end + + if device:supports_capability(capabilities.rainSensor) then + profile_name = profile_name .. "-rain" + end + + if device:supports_capability(capabilities.temperatureAlarm) then + profile_name = profile_name .. "-freeze" + end + + if device:supports_capability(capabilities.waterSensor) then + profile_name = profile_name .. "-leak" + end + + if device:supports_capability(capabilities.flowMeasurement) then + profile_name = profile_name .. "-flow" + end + + if device:supports_capability(capabilities.button) then + profile_name = profile_name .. "-button" + end + + if battery_supported == fields.battery_support.BATTERY_PERCENTAGE then + profile_name = profile_name .. "-battery" + elseif battery_supported == fields.battery_support.BATTERY_LEVEL then + profile_name = profile_name .. "-batteryLevel" + end + + if device:supports_capability(capabilities.hardwareFault) then + profile_name = profile_name .. "-fault" + end + + local concatenated_preferences = supports_sensitivity_preferences(device) + profile_name = profile_name .. concatenated_preferences + + if device:supports_capability(capabilities.motionSensor) then + local occupancy_support = "-motion" + -- If the Occupancy Sensing Cluster’s revision is >= 5 (corresponds to Lua Libs version 13+), and any of the AIR / RAD / RFS / VIS + -- features are supported by the device, use the presenceSensor capability. Otherwise, use the motionSensor capability. Currently, + -- presenceSensor only used for devices fingerprinting to the motion-illuminance-temperature-humidity-battery profile. + if profile_name == "-illuminance-temperature-humidity-battery" and version.api >= 13 then + if #device:get_endpoints(clusters.OccupancySensing.ID, {feature_bitmap = clusters.OccupancySensing.types.Feature.ACTIVE_INFRARED}) > 0 or + #device:get_endpoints(clusters.OccupancySensing.ID, {feature_bitmap = clusters.OccupancySensing.types.Feature.RADAR}) > 0 or + #device:get_endpoints(clusters.OccupancySensing.ID, {feature_bitmap = clusters.OccupancySensing.types.Feature.RF_SENSING}) > 0 or + #device:get_endpoints(clusters.OccupancySensing.ID, {feature_bitmap = clusters.OccupancySensing.types.Feature.VISION}) then + occupancy_support = "-presence" + end + end + profile_name = occupancy_support .. profile_name + end + + -- remove leading "-" + profile_name = string.sub(profile_name, 2) + + device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) + device:try_update_metadata({profile = profile_name}) +end + +return DeviceConfiguration \ No newline at end of file diff --git a/drivers/SmartThings/matter-sensor/src/embedded-cluster-utils.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/embedded_cluster_utils.lua similarity index 70% rename from drivers/SmartThings/matter-sensor/src/embedded-cluster-utils.lua rename to drivers/SmartThings/matter-sensor/src/sensor_utils/embedded_cluster_utils.lua index 244ff77fc2..e2384098d9 100644 --- a/drivers/SmartThings/matter-sensor/src/embedded-cluster-utils.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/embedded_cluster_utils.lua @@ -1,25 +1,28 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local clusters = require "st.matter.clusters" local utils = require "st.utils" -- Include driver-side definitions when lua libs api version is < 10 local version = require "version" if version.api < 10 then - clusters.AirQuality = require "AirQuality" - clusters.CarbonMonoxideConcentrationMeasurement = require "CarbonMonoxideConcentrationMeasurement" - clusters.CarbonDioxideConcentrationMeasurement = require "CarbonDioxideConcentrationMeasurement" - clusters.FormaldehydeConcentrationMeasurement = require "FormaldehydeConcentrationMeasurement" - clusters.NitrogenDioxideConcentrationMeasurement = require "NitrogenDioxideConcentrationMeasurement" - clusters.OzoneConcentrationMeasurement = require "OzoneConcentrationMeasurement" - clusters.Pm1ConcentrationMeasurement = require "Pm1ConcentrationMeasurement" - clusters.Pm10ConcentrationMeasurement = require "Pm10ConcentrationMeasurement" - clusters.Pm25ConcentrationMeasurement = require "Pm25ConcentrationMeasurement" - clusters.RadonConcentrationMeasurement = require "RadonConcentrationMeasurement" - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "TotalVolatileOrganicCompoundsConcentrationMeasurement" - clusters.SmokeCoAlarm = require "SmokeCoAlarm" + clusters.AirQuality = require "embedded_clusters.AirQuality" + clusters.CarbonMonoxideConcentrationMeasurement = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement" + clusters.CarbonDioxideConcentrationMeasurement = require "embedded_clusters.CarbonDioxideConcentrationMeasurement" + clusters.FormaldehydeConcentrationMeasurement = require "embedded_clusters.FormaldehydeConcentrationMeasurement" + clusters.NitrogenDioxideConcentrationMeasurement = require "embedded_clusters.NitrogenDioxideConcentrationMeasurement" + clusters.OzoneConcentrationMeasurement = require "embedded_clusters.OzoneConcentrationMeasurement" + clusters.Pm1ConcentrationMeasurement = require "embedded_clusters.Pm1ConcentrationMeasurement" + clusters.Pm10ConcentrationMeasurement = require "embedded_clusters.Pm10ConcentrationMeasurement" + clusters.Pm25ConcentrationMeasurement = require "embedded_clusters.Pm25ConcentrationMeasurement" + clusters.RadonConcentrationMeasurement = require "embedded_clusters.RadonConcentrationMeasurement" + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement" + clusters.SmokeCoAlarm = require "embedded_clusters.SmokeCoAlarm" end if version.api < 11 then - clusters.BooleanStateConfiguration = require "BooleanStateConfiguration" + clusters.BooleanStateConfiguration = require "embedded_clusters.BooleanStateConfiguration" end local embedded_cluster_utils = {} diff --git a/drivers/SmartThings/matter-sensor/src/sensor_utils/fields.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/fields.lua new file mode 100644 index 0000000000..f0b2a5c7a6 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/fields.lua @@ -0,0 +1,50 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" + +local SensorFields = {} + +SensorFields.TEMP_BOUND_RECEIVED = "__temp_bound_received" +SensorFields.TEMP_MIN = "__temp_min" +SensorFields.TEMP_MAX = "__temp_max" +SensorFields.FLOW_BOUND_RECEIVED = "__flow_bound_received" +SensorFields.FLOW_MIN = "__flow_min" +SensorFields.FLOW_MAX = "__flow_max" + +SensorFields.battery_support = { + NO_BATTERY = "NO_BATTERY", + BATTERY_LEVEL = "BATTERY_LEVEL", + BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE" +} + +SensorFields.BOOLEAN_DEVICE_TYPE_INFO = { + ["RAIN_SENSOR"] = { id = 0x0044, sensitivity_preference = "rainSensitivity", sensitivity_max = "rainMax" }, + ["WATER_FREEZE_DETECTOR"] = { id = 0x0041, sensitivity_preference = "freezeSensitivity", sensitivity_max = "freezeMax" }, + ["WATER_LEAK_DETECTOR"] = { id = 0x0043, sensitivity_preference = "leakSensitivity", sensitivity_max = "leakMax" }, + ["CONTACT_SENSOR"] = { id = 0x0015, sensitivity_preference = "N/A", sensitivity_max = "N/A" }, +} + +SensorFields.ORDERED_DEVICE_TYPE_INFO = { + "RAIN_SENSOR", + "WATER_FREEZE_DETECTOR", + "WATER_LEAK_DETECTOR", + "CONTACT_SENSOR" +} + +SensorFields.BOOLEAN_CAP_EVENT_MAP = { + [true] = { + ["WATER_FREEZE_DETECTOR"] = capabilities.temperatureAlarm.temperatureAlarm.freeze(), + ["WATER_LEAK_DETECTOR"] = capabilities.waterSensor.water.wet(), + ["RAIN_SENSOR"] = capabilities.rainSensor.rain.detected(), + ["CONTACT_SENSOR"] = capabilities.contactSensor.contact.closed(), + }, + [false] = { + ["WATER_FREEZE_DETECTOR"] = capabilities.temperatureAlarm.temperatureAlarm.cleared(), + ["WATER_LEAK_DETECTOR"] = capabilities.waterSensor.water.dry(), + ["RAIN_SENSOR"] = capabilities.rainSensor.rain.undetected(), + ["CONTACT_SENSOR"] = capabilities.contactSensor.contact.open(), + } +} + +return SensorFields diff --git a/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua new file mode 100644 index 0000000000..5a0421fb0c --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua @@ -0,0 +1,24 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local utils = {} + +function utils.get_field_for_endpoint(device, field, endpoint) + return device:get_field(string.format("%s_%d", field, endpoint)) +end + +function utils.set_field_for_endpoint(device, field, endpoint, value, additional_params) + device:set_field(string.format("%s_%d", field, endpoint), value, additional_params) +end + +function utils.tbl_contains(array, value) + if value == nil then return false end + for _, element in pairs(array or {}) do + if element == value then + return true + end + end + return false +end + +return utils diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers.lua new file mode 100644 index 0000000000..3bf4c46f73 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" +local sub_drivers = { + lazy_load_if_possible("sub_drivers.air_quality_sensor"), + lazy_load_if_possible("sub_drivers.smoke_co_alarm"), + lazy_load_if_possible("sub_drivers.bosch_button_contact"), +} +return sub_drivers diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_handlers/attribute_handlers.lua new file mode 100644 index 0000000000..345f7c5782 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_handlers/attribute_handlers.lua @@ -0,0 +1,74 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local st_utils = require "st.utils" +local capabilities = require "st.capabilities" +local aqs_fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" + +local AirQualitySensorAttributeHandlers = {} + + +-- [[ GENERIC CONCENTRATION MEASUREMENT CLUSTER ATTRIBUTES ]] + +function AirQualitySensorAttributeHandlers.measurement_unit_factory(capability_name) + return function(driver, device, ib, response) + device:set_field(capability_name.."_unit", ib.data.value, {persist = true}) + end +end + +function AirQualitySensorAttributeHandlers.level_value_factory(attribute) + return function(driver, device, ib, response) + device:emit_event_for_endpoint(ib.endpoint_id, attribute(aqs_fields.level_strings[ib.data.value])) + end +end + +function AirQualitySensorAttributeHandlers.measured_value_factory(capability_name, attribute, target_unit) + return function(driver, device, ib, response) + if ib.data.value then + local reporting_unit = device:get_field(capability_name.."_unit") or aqs_fields.unit_default[capability_name] + local conversion_function = aqs_fields.conversion_tables[reporting_unit][target_unit] + if conversion_function then + local converted_value = conversion_function(ib.data.value) + device:emit_event_for_endpoint(ib.endpoint_id, attribute({value = converted_value, unit = aqs_fields.unit_strings[target_unit]})) + -- handle case where device profile supports both fineDustLevel and dustLevel + if capability_name == capabilities.fineDustSensor.NAME and device:supports_capability(capabilities.dustSensor) then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.dustSensor.fineDustLevel({value = converted_value, unit = aqs_fields.unit_strings[target_unit]})) + end + else + device.log.info_with({hub_logs=true}, string.format("Unsupported unit conversion from %s to %s", aqs_fields.unit_strings[reporting_unit], aqs_fields.unit_strings[target_unit])) + end + end + end +end + + +-- [[ AIR QUALITY CLUSTER ATTRIBUTES ]] -- + +function AirQualitySensorAttributeHandlers.air_quality_handler(driver, device, ib, response) + local state = ib.data.value + if state == 0 then -- Unknown + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.unknown()) + elseif state == 1 then -- Good + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.good()) + elseif state == 2 then -- Fair + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.moderate()) + elseif state == 3 then -- Moderate + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.slightlyUnhealthy()) + elseif state == 4 then -- Poor + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.unhealthy()) + elseif state == 5 then -- VeryPoor + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.veryUnhealthy()) + elseif state == 6 then -- ExtremelyPoor + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.hazardous()) + end +end + + +-- [[ PRESSURE MEASUREMENT CLUSTER ATTRIBUTES ]] -- + +function AirQualitySensorAttributeHandlers.pressure_measured_value_handler(driver, device, ib, response) + local pressure = st_utils.round(ib.data.value / 10.0) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.atmosphericPressureMeasurement.atmosphericPressure(pressure)) +end + +return AirQualitySensorAttributeHandlers diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/device_configuration.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/device_configuration.lua new file mode 100644 index 0000000000..6e1f1a5d81 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/device_configuration.lua @@ -0,0 +1,89 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local version = require "version" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local embedded_cluster_utils = require "sensor_utils.embedded_cluster_utils" +local fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" + +local DeviceConfiguration = {} + +function DeviceConfiguration.supported_level_measurements(device) + local measurement_caps, level_caps = {}, {} + for _, cap in ipairs(fields.CONCENTRATION_MEASUREMENT_PROFILE_ORDERING) do + local cap_id = cap.ID + local cluster = fields.CONCENTRATION_MEASUREMENT_MAP[cap][2] + -- capability describes either a HealthConcern or Measurement/Sensor + if (cap_id:match("HealthConcern$")) then + local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) + if #attr_eps > 0 then + table.insert(level_caps, cap_id) + end + elseif (cap_id:match("Measurement$") or cap_id:match("Sensor$")) then + local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.NUMERIC_MEASUREMENT }) + if #attr_eps > 0 then + table.insert(measurement_caps, cap_id) + end + end + end + return measurement_caps, level_caps +end + +-- Match Modular Profile +function DeviceConfiguration.match_profile(device) + local temp_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureMeasurement.ID) + local humidity_eps = embedded_cluster_utils.get_endpoints(device, clusters.RelativeHumidityMeasurement.ID) + + local optional_supported_component_capabilities = {} + local main_component_capabilities = {} + local profile_name + local MAIN_COMPONENT_IDX = 1 + local CAPABILITIES_LIST_IDX = 2 + + if #temp_eps > 0 then + table.insert(main_component_capabilities, capabilities.temperatureMeasurement.ID) + end + if #humidity_eps > 0 then + table.insert(main_component_capabilities, capabilities.relativeHumidityMeasurement.ID) + end + + local measurement_caps, level_caps = DeviceConfiguration.supported_level_measurements(device) + + for _, cap_id in ipairs(measurement_caps) do + table.insert(main_component_capabilities, cap_id) + end + + for _, cap_id in ipairs(level_caps) do + table.insert(main_component_capabilities, cap_id) + end + + table.insert(optional_supported_component_capabilities, {"main", main_component_capabilities}) + + if #temp_eps > 0 and #humidity_eps > 0 then + profile_name = "aqs-modular-temp-humidity" + elseif #temp_eps > 0 then + profile_name = "aqs-modular-temp" + elseif #humidity_eps > 0 then + profile_name = "aqs-modular-humidity" + else + profile_name = "aqs-modular" + end + + device:try_update_metadata({profile = profile_name, optional_component_capabilities = optional_supported_component_capabilities}) + device:set_field(fields.MODULAR_PROFILE_UPDATED, true) + + -- earlier modular profile gating (min api v14, rpc 8) ensures we are running >= 0.57 FW. + -- This gating specifies a workaround required only for 0.57 FW, which is not needed for 0.58 and higher. + if version.api < 15 or version.rpc < 9 then + -- add mandatory capabilities for subscription + local total_supported_capabilities = optional_supported_component_capabilities + table.insert(total_supported_capabilities[MAIN_COMPONENT_IDX][CAPABILITIES_LIST_IDX], capabilities.airQualityHealthConcern.ID) + table.insert(total_supported_capabilities[MAIN_COMPONENT_IDX][CAPABILITIES_LIST_IDX], capabilities.refresh.ID) + table.insert(total_supported_capabilities[MAIN_COMPONENT_IDX][CAPABILITIES_LIST_IDX], capabilities.firmwareUpdate.ID) + + device:set_field(fields.SUPPORTED_COMPONENT_CAPABILITIES, total_supported_capabilities, { persist = true }) + end +end + +return DeviceConfiguration \ No newline at end of file diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/fields.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/fields.lua new file mode 100644 index 0000000000..0d3c5dd21c --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/fields.lua @@ -0,0 +1,180 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local version = require "version" +local utils = require "st.utils" +local clusters = require "st.matter.clusters" +local capabilities = require "st.capabilities" + +-- Include driver-side definitions when lua libs api version is < 10 +if version.api < 10 then + clusters.AirQuality = require "embedded_clusters.AirQuality" + clusters.CarbonMonoxideConcentrationMeasurement = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement" + clusters.CarbonDioxideConcentrationMeasurement = require "embedded_clusters.CarbonDioxideConcentrationMeasurement" + clusters.FormaldehydeConcentrationMeasurement = require "embedded_clusters.FormaldehydeConcentrationMeasurement" + clusters.NitrogenDioxideConcentrationMeasurement = require "embedded_clusters.NitrogenDioxideConcentrationMeasurement" + clusters.OzoneConcentrationMeasurement = require "embedded_clusters.OzoneConcentrationMeasurement" + clusters.Pm1ConcentrationMeasurement = require "embedded_clusters.Pm1ConcentrationMeasurement" + clusters.Pm10ConcentrationMeasurement = require "embedded_clusters.Pm10ConcentrationMeasurement" + clusters.Pm25ConcentrationMeasurement = require "embedded_clusters.Pm25ConcentrationMeasurement" + clusters.RadonConcentrationMeasurement = require "embedded_clusters.RadonConcentrationMeasurement" + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement" +end + + +local AirQualitySensorFields = {} + +AirQualitySensorFields.AIR_QUALITY_SENSOR_DEVICE_TYPE_ID = 0x002C + +AirQualitySensorFields.MODULAR_PROFILE_UPDATED = "__modular_profile_updated" + +AirQualitySensorFields.SUPPORTED_COMPONENT_CAPABILITIES = "__supported_component_capabilities" + +AirQualitySensorFields.units_required = { + clusters.CarbonMonoxideConcentrationMeasurement, + clusters.CarbonDioxideConcentrationMeasurement, + clusters.NitrogenDioxideConcentrationMeasurement, + clusters.OzoneConcentrationMeasurement, + clusters.FormaldehydeConcentrationMeasurement, + clusters.Pm1ConcentrationMeasurement, + clusters.Pm25ConcentrationMeasurement, + clusters.Pm10ConcentrationMeasurement, + clusters.RadonConcentrationMeasurement, + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement +} + +AirQualitySensorFields.supported_profiles = +{ + "aqs", + "aqs-temp-humidity-all-level-all-meas", + "aqs-temp-humidity-all-level", + "aqs-temp-humidity-all-meas", + "aqs-temp-humidity-co2-pm25-tvoc-meas", + "aqs-temp-humidity-co2-pm1-pm25-pm10-meas", + "aqs-temp-humidity-tvoc-level-pm25-meas", + "aqs-temp-humidity-tvoc-meas", +} + +AirQualitySensorFields.CONCENTRATION_MEASUREMENT_MAP = { + [capabilities.carbonMonoxideMeasurement] = {"-co", clusters.CarbonMonoxideConcentrationMeasurement, "N/A"}, + [capabilities.carbonMonoxideHealthConcern] = {"-co", clusters.CarbonMonoxideConcentrationMeasurement, capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues}, + [capabilities.carbonDioxideMeasurement] = {"-co2", clusters.CarbonDioxideConcentrationMeasurement, "N/A"}, + [capabilities.carbonDioxideHealthConcern] = {"-co2", clusters.CarbonDioxideConcentrationMeasurement, capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues}, + [capabilities.nitrogenDioxideMeasurement] = {"-no2", clusters.NitrogenDioxideConcentrationMeasurement, "N/A"}, + [capabilities.nitrogenDioxideHealthConcern] = {"-no2", clusters.NitrogenDioxideConcentrationMeasurement, capabilities.nitrogenDioxideHealthConcern.supportedNitrogenDioxideValues}, + [capabilities.ozoneMeasurement] = {"-ozone", clusters.OzoneConcentrationMeasurement, "N/A"}, + [capabilities.ozoneHealthConcern] = {"-ozone", clusters.OzoneConcentrationMeasurement, capabilities.ozoneHealthConcern.supportedOzoneValues}, + [capabilities.formaldehydeMeasurement] = {"-ch2o", clusters.FormaldehydeConcentrationMeasurement, "N/A"}, + [capabilities.formaldehydeHealthConcern] = {"-ch2o", clusters.FormaldehydeConcentrationMeasurement, capabilities.formaldehydeHealthConcern.supportedFormaldehydeValues}, + [capabilities.veryFineDustSensor] = {"-pm1", clusters.Pm1ConcentrationMeasurement, "N/A"}, + [capabilities.veryFineDustHealthConcern] = {"-pm1", clusters.Pm1ConcentrationMeasurement, capabilities.veryFineDustHealthConcern.supportedVeryFineDustValues}, + [capabilities.fineDustSensor] = {"-pm25", clusters.Pm25ConcentrationMeasurement, "N/A"}, + [capabilities.fineDustHealthConcern] = {"-pm25", clusters.Pm25ConcentrationMeasurement, capabilities.fineDustHealthConcern.supportedFineDustValues}, + [capabilities.dustSensor] = {"-pm10", clusters.Pm10ConcentrationMeasurement, "N/A"}, + [capabilities.dustHealthConcern] = {"-pm10", clusters.Pm10ConcentrationMeasurement, capabilities.dustHealthConcern.supportedDustValues}, + [capabilities.radonMeasurement] = {"-radon", clusters.RadonConcentrationMeasurement, "N/A"}, + [capabilities.radonHealthConcern] = {"-radon", clusters.RadonConcentrationMeasurement, capabilities.radonHealthConcern.supportedRadonValues}, + [capabilities.tvocMeasurement] = {"-tvoc", clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement, "N/A"}, + [capabilities.tvocHealthConcern] = {"-tvoc", clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement, capabilities.tvocHealthConcern.supportedTvocValues}, +} + + +AirQualitySensorFields.CONCENTRATION_MEASUREMENT_PROFILE_ORDERING = { + capabilities.carbonMonoxideMeasurement, + capabilities.carbonMonoxideHealthConcern, + capabilities.carbonDioxideMeasurement, + capabilities.carbonDioxideHealthConcern, + capabilities.nitrogenDioxideMeasurement, + capabilities.nitrogenDioxideHealthConcern, + capabilities.ozoneMeasurement, + capabilities.ozoneHealthConcern, + capabilities.formaldehydeMeasurement, + capabilities.formaldehydeHealthConcern, + capabilities.veryFineDustSensor, + capabilities.veryFineDustHealthConcern, + capabilities.fineDustSensor, + capabilities.fineDustHealthConcern, + capabilities.dustSensor, + capabilities.dustHealthConcern, + capabilities.radonMeasurement, + capabilities.radonHealthConcern, + capabilities.tvocMeasurement, + capabilities.tvocHealthConcern, +} + +AirQualitySensorFields.units = { + PPM = 0, + PPB = 1, + PPT = 2, + MGM3 = 3, + UGM3 = 4, + NGM3 = 5, + PM3 = 6, + BQM3 = 7, + PCIL = 0xFF -- not in matter spec +} + +local units = AirQualitySensorFields.units -- copy to remove the prefix in uses below + +AirQualitySensorFields.unit_strings = { + [units.PPM] = "ppm", + [units.PPB] = "ppb", + [units.PPT] = "ppt", + [units.MGM3] = "mg/m^3", + [units.NGM3] = "ng/m^3", + [units.UGM3] = "μg/m^3", + [units.BQM3] = "Bq/m^3", + [units.PCIL] = "pCi/L" +} + +AirQualitySensorFields.unit_default = { + [capabilities.carbonMonoxideMeasurement.NAME] = units.PPM, + [capabilities.carbonDioxideMeasurement.NAME] = units.PPM, + [capabilities.nitrogenDioxideMeasurement.NAME] = units.PPM, + [capabilities.ozoneMeasurement.NAME] = units.PPM, + [capabilities.formaldehydeMeasurement.NAME] = units.PPM, + [capabilities.veryFineDustSensor.NAME] = units.UGM3, + [capabilities.fineDustSensor.NAME] = units.UGM3, + [capabilities.dustSensor.NAME] = units.UGM3, + [capabilities.radonMeasurement.NAME] = units.BQM3, + [capabilities.tvocMeasurement.NAME] = units.PPB -- TVOC is typically within the range of 0-5500 ppb, with good to moderate values being < 660 ppb +} + +-- All ConcentrationMeasurement clusters inherit from the same base cluster definitions, +-- so CarbonMonoxideConcentrationMeasurement is used below but the same enum types exist +-- in all ConcentrationMeasurement clusters +AirQualitySensorFields.level_strings = { + [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.UNKNOWN] = "unknown", + [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.LOW] = "good", + [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.MEDIUM] = "moderate", + [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.HIGH] = "unhealthy", + [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.CRITICAL] = "hazardous", +} + +AirQualitySensorFields.conversion_tables = { + [units.PPM] = { + [units.PPM] = function(value) return utils.round(value) end, + [units.PPB] = function(value) return utils.round(value * (10^3)) end + }, + [units.PPB] = { + [units.PPM] = function(value) return utils.round(value/(10^3)) end, + [units.PPB] = function(value) return utils.round(value) end + }, + [units.PPT] = { + [units.PPM] = function(value) return utils.round(value/(10^6)) end + }, + [units.MGM3] = { + [units.UGM3] = function(value) return utils.round(value * (10^3)) end + }, + [units.UGM3] = { + [units.UGM3] = function(value) return utils.round(value) end + }, + [units.NGM3] = { + [units.UGM3] = function(value) return utils.round(value/(10^3)) end + }, + [units.BQM3] = { + [units.PCIL] = function(value) return utils.round(value/37) end + } +} + +return AirQualitySensorFields diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua new file mode 100644 index 0000000000..bc45021341 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua @@ -0,0 +1,89 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local clusters = require "st.matter.clusters" +local sensor_utils = require "sensor_utils.utils" +local embedded_cluster_utils = require "sensor_utils.embedded_cluster_utils" +local fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" + +local LegacyDeviceConfiguration = {} + +function LegacyDeviceConfiguration.create_level_measurement_profile(device) + local meas_name, level_name = "", "" + for _, cap in ipairs(fields.CONCENTRATION_MEASUREMENT_PROFILE_ORDERING) do + local cap_id = cap.ID + local cluster = fields.CONCENTRATION_MEASUREMENT_MAP[cap][2] + -- capability describes either a HealthConcern or Measurement/Sensor + if (cap_id:match("HealthConcern$")) then + local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) + if #attr_eps > 0 then + level_name = level_name .. fields.CONCENTRATION_MEASUREMENT_MAP[cap][1] + end + elseif (cap_id:match("Measurement$") or cap_id:match("Sensor$")) then + local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.NUMERIC_MEASUREMENT }) + if #attr_eps > 0 then + meas_name = meas_name .. fields.CONCENTRATION_MEASUREMENT_MAP[cap][1] + end + end + end + return meas_name, level_name +end + +-- MATCH STATIC PROFILE +function LegacyDeviceConfiguration.match_profile(device) + local temp_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureMeasurement.ID) + local humidity_eps = embedded_cluster_utils.get_endpoints(device, clusters.RelativeHumidityMeasurement.ID) + + local profile_name = "aqs" + + if #temp_eps > 0 then + profile_name = profile_name .. "-temp" + end + if #humidity_eps > 0 then + profile_name = profile_name .. "-humidity" + end + + local meas_name, level_name = LegacyDeviceConfiguration.create_level_measurement_profile(device) + + -- If all endpoints are supported, use '-all' in the profile name so that it + -- remains under the profile name character limit + if level_name == "-co-co2-no2-ozone-ch2o-pm1-pm25-pm10-radon-tvoc" then + level_name = "-all" + end + if level_name ~= "" then + profile_name = profile_name .. level_name .. "-level" + end + + -- If all endpoints are supported, use '-all' in the profile name so that it + -- remains under the profile name character limit + if meas_name == "-co-co2-no2-ozone-ch2o-pm1-pm25-pm10-radon-tvoc" then + meas_name = "-all" + end + if meas_name ~= "" then + profile_name = profile_name .. meas_name .. "-meas" + end + + if not sensor_utils.tbl_contains(fields.supported_profiles, profile_name) then + device.log.warn_with({hub_logs=true}, string.format("No matching profile for device. Tried to use profile %s", profile_name)) + + local function meas_find(sub_name) + return string.match(meas_name, sub_name) ~= nil + end + + -- try to best match to existing profiles + -- these checks, meas_find("co%-") and meas_find("co$"), match the string to co and NOT co2. + if meas_find("co%-") or meas_find("co$") or meas_find("no2") or meas_find("ozone") or meas_find("ch2o") or + meas_find("pm1") or meas_find("pm10") or meas_find("radon") then + profile_name = "aqs-temp-humidity-all-meas" + elseif #humidity_eps > 0 or #temp_eps > 0 or meas_find("co2") or meas_find("pm25") or meas_find("tvoc") then + profile_name = "aqs-temp-humidity-co2-pm25-tvoc-meas" + else + -- device only supports air quality at this point + profile_name = "aqs" + end + end + device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s", profile_name)) + device:try_update_metadata({profile = profile_name}) +end + +return LegacyDeviceConfiguration \ No newline at end of file diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua new file mode 100644 index 0000000000..1a36f5f920 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua @@ -0,0 +1,80 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local embedded_cluster_utils = require "sensor_utils.embedded_cluster_utils" +local fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" + + +local AirQualitySensorUtils = {} + +function AirQualitySensorUtils.supports_capability_by_id_modular(device, capability, component) + if not device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES) then + device.log.warn_with({hub_logs = true}, "Device has overriden supports_capability_by_id, but does not have supported capabilities set.") + return false + end + for _, component_capabilities in ipairs(device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES)) do + local comp_id = component_capabilities[1] + local capability_ids = component_capabilities[2] + if (component == nil) or (component == comp_id) then + for _, cap in ipairs(capability_ids) do + if cap == capability then + return true + end + end + end + end + return false +end + +local function get_supported_health_concern_values_for_air_quality(device) + local health_concern_datatype = capabilities.airQualityHealthConcern.airQualityHealthConcern + local supported_values = {health_concern_datatype.unknown.NAME, health_concern_datatype.good.NAME, health_concern_datatype.unhealthy.NAME} + if #embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID, { feature_bitmap = clusters.AirQuality.types.Feature.FAIR }) > 0 then + table.insert(supported_values, health_concern_datatype.moderate.NAME) + end + if #embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID, { feature_bitmap = clusters.AirQuality.types.Feature.MODERATE }) > 0 then + table.insert(supported_values, health_concern_datatype.slightlyUnhealthy.NAME) + end + if #embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID, { feature_bitmap = clusters.AirQuality.types.Feature.VERY_POOR }) > 0 then + table.insert(supported_values, health_concern_datatype.veryUnhealthy.NAME) + end + if #embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID, { feature_bitmap = clusters.AirQuality.types.Feature.EXTREMELY_POOR }) > 0 then + table.insert(supported_values, health_concern_datatype.hazardous.NAME) + end + return supported_values +end + +local function get_supported_health_concern_values_for_concentration_cluster(device, cluster) + -- note: health_concern_datatype is generic since all the healthConcern capabilities' datatypes are equivalent to those in airQualityHealthConcern + local health_concern_datatype = capabilities.airQualityHealthConcern.airQualityHealthConcern + local supported_values = {health_concern_datatype.unknown.NAME, health_concern_datatype.good.NAME, health_concern_datatype.unhealthy.NAME} + if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.MEDIUM_LEVEL }) > 0 then + table.insert(supported_values, health_concern_datatype.moderate.NAME) + end + if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.CRITICAL_LEVEL }) > 0 then + table.insert(supported_values, health_concern_datatype.hazardous.NAME) + end + return supported_values +end + +function AirQualitySensorUtils.set_supported_health_concern_values(device) + -- handle AQ Health Concern, since this is a mandatory capability + local supported_aqs_values = get_supported_health_concern_values_for_air_quality(device) + local aqs_ep_ids = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) or {} + device:emit_event_for_endpoint(aqs_ep_ids[1], capabilities.airQualityHealthConcern.supportedAirQualityValues(supported_aqs_values, { visibility = { displayed = false }})) + + for _, capability in ipairs(fields.CONCENTRATION_MEASUREMENT_PROFILE_ORDERING) do + -- all of these capabilities are optional, and capabilities stored in this field are for either a HealthConcern or a Measurement/Sensor + if device:supports_capability_by_id(capability.ID) and capability.ID:match("HealthConcern$") then + local cluster_info = fields.CONCENTRATION_MEASUREMENT_MAP[capability][2] + local supported_values_setter = fields.CONCENTRATION_MEASUREMENT_MAP[capability][3] + local supported_values = get_supported_health_concern_values_for_concentration_cluster(device, cluster_info) + local cluster_ep_ids = embedded_cluster_utils.get_endpoints(device, cluster_info.ID, { feature_bitmap = cluster_info.types.Feature.LEVEL_INDICATION }) or {} -- cluster associated with the supported capability + device:emit_event_for_endpoint(cluster_ep_ids[1], supported_values_setter(supported_values, { visibility = { displayed = false }})) + end + end +end + +return AirQualitySensorUtils diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/can_handle.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/can_handle.lua new file mode 100644 index 0000000000..9c1a1ab762 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_matter_air_quality_sensor(opts, driver, device) + local fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == fields.AIR_QUALITY_SENSOR_DEVICE_TYPE_ID then + return true, require("sub_drivers.air_quality_sensor") + end + end + end + + return false +end + +return is_matter_air_quality_sensor diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua new file mode 100644 index 0000000000..61280fd107 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua @@ -0,0 +1,154 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local version = require "version" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local aqs_utils = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.utils" +local fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" +local attribute_handlers = require "sub_drivers.air_quality_sensor.air_quality_sensor_handlers.attribute_handlers" + +-- Include driver-side definitions when lua libs api version is < 10 +if version.api < 10 then + clusters.AirQuality = require "embedded_clusters.AirQuality" + clusters.CarbonMonoxideConcentrationMeasurement = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement" + clusters.CarbonDioxideConcentrationMeasurement = require "embedded_clusters.CarbonDioxideConcentrationMeasurement" + clusters.FormaldehydeConcentrationMeasurement = require "embedded_clusters.FormaldehydeConcentrationMeasurement" + clusters.NitrogenDioxideConcentrationMeasurement = require "embedded_clusters.NitrogenDioxideConcentrationMeasurement" + clusters.OzoneConcentrationMeasurement = require "embedded_clusters.OzoneConcentrationMeasurement" + clusters.Pm1ConcentrationMeasurement = require "embedded_clusters.Pm1ConcentrationMeasurement" + clusters.Pm10ConcentrationMeasurement = require "embedded_clusters.Pm10ConcentrationMeasurement" + clusters.Pm25ConcentrationMeasurement = require "embedded_clusters.Pm25ConcentrationMeasurement" + clusters.RadonConcentrationMeasurement = require "embedded_clusters.RadonConcentrationMeasurement" + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement" +end + +-- AIR QUALITY SENSOR LIFECYCLE HANDLERS -- + +local AirQualitySensorLifecycleHandlers = {} + +function AirQualitySensorLifecycleHandlers.do_configure(driver, device) + -- we have to read the unit before reports of values will do anything + for _, cluster in ipairs(fields.units_required) do + device:send(cluster.attributes.MeasurementUnit:read(device)) + end + if version.api >= 14 and version.rpc >= 8 then + local modular_device_cfg = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.device_configuration" + modular_device_cfg.match_profile(device) + else + local legacy_device_cfg = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.legacy_device_configuration" + legacy_device_cfg.match_profile(device) + end +end + +function AirQualitySensorLifecycleHandlers.driver_switched(driver, device) + -- we have to read the unit before reports of values will do anything + for _, cluster in ipairs(fields.units_required) do + device:send(cluster.attributes.MeasurementUnit:read(device)) + end + if version.api >= 14 and version.rpc >= 8 then + local modular_device_cfg = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.device_configuration" + modular_device_cfg.match_profile(device) + else + local legacy_device_cfg = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.legacy_device_configuration" + legacy_device_cfg.match_profile(device) + end +end + +function AirQualitySensorLifecycleHandlers.device_init(driver, device) + if device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES) and (version.api < 15 or version.rpc < 9) then + -- assume that device is using a modular profile on 0.57 FW, override supports_capability_by_id + -- library function to utilize optional capabilities + device:extend_device("supports_capability_by_id", aqs_utils.supports_capability_by_id_modular) + end + aqs_utils.set_supported_health_concern_values(device) + device:subscribe() +end + +function AirQualitySensorLifecycleHandlers.info_changed(driver, device, event, args) + if device.profile.id ~= args.old_st_store.profile.id or device:get_field(fields.MODULAR_PROFILE_UPDATED) then + if device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES) then + --re-up subscription with new capabilities using the modular supports_capability override + device:extend_device("supports_capability_by_id", aqs_utils.supports_capability_by_id_modular) + end + device:set_field(fields.MODULAR_PROFILE_UPDATED, nil) + aqs_utils.set_supported_health_concern_values(device) + device:subscribe() + end +end + + +-- SUBDRIVER TEMPLATE -- + +local matter_air_quality_sensor_handler = { + NAME = "matter-air-quality-sensor", + lifecycle_handlers = { + doConfigure = AirQualitySensorLifecycleHandlers.do_configure, + driverSwitched = AirQualitySensorLifecycleHandlers.driver_switched, + infoChanged = AirQualitySensorLifecycleHandlers.info_changed, + init = AirQualitySensorLifecycleHandlers.device_init, + }, + matter_handlers = { + attr = { + [clusters.AirQuality.ID] = { + [clusters.AirQuality.attributes.AirQuality.ID] = attribute_handlers.air_quality_handler, + }, + [clusters.CarbonDioxideConcentrationMeasurement.ID] = { + [clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.carbonDioxideMeasurement.NAME, capabilities.carbonDioxideMeasurement.carbonDioxide, fields.units.PPM), + [clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.carbonDioxideMeasurement.NAME), + [clusters.CarbonDioxideConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern), + }, + [clusters.CarbonMonoxideConcentrationMeasurement.ID] = { + [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.carbonMonoxideMeasurement.NAME, capabilities.carbonMonoxideMeasurement.carbonMonoxideLevel, fields.units.PPM), + [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.carbonMonoxideMeasurement.NAME), + [clusters.CarbonMonoxideConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.carbonMonoxideHealthConcern.carbonMonoxideHealthConcern), + }, + [clusters.FormaldehydeConcentrationMeasurement.ID] = { + [clusters.FormaldehydeConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.formaldehydeMeasurement.NAME, capabilities.formaldehydeMeasurement.formaldehydeLevel, fields.units.PPM), + [clusters.FormaldehydeConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.formaldehydeMeasurement.NAME), + [clusters.FormaldehydeConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.formaldehydeHealthConcern.formaldehydeHealthConcern), + }, + [clusters.NitrogenDioxideConcentrationMeasurement.ID] = { + [clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.nitrogenDioxideMeasurement.NAME, capabilities.nitrogenDioxideMeasurement.nitrogenDioxide, fields.units.PPM), + [clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.nitrogenDioxideMeasurement.NAME), + [clusters.NitrogenDioxideConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.nitrogenDioxideHealthConcern.nitrogenDioxideHealthConcern) + }, + [clusters.OzoneConcentrationMeasurement.ID] = { + [clusters.OzoneConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.ozoneMeasurement.NAME, capabilities.ozoneMeasurement.ozone, fields.units.PPM), + [clusters.OzoneConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.ozoneMeasurement.NAME), + [clusters.OzoneConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.ozoneHealthConcern.ozoneHealthConcern) + }, + [clusters.Pm1ConcentrationMeasurement.ID] = { + [clusters.Pm1ConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.veryFineDustSensor.NAME, capabilities.veryFineDustSensor.veryFineDustLevel, fields.units.UGM3), + [clusters.Pm1ConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.veryFineDustSensor.NAME), + [clusters.Pm1ConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.veryFineDustHealthConcern.veryFineDustHealthConcern), + }, + [clusters.Pm10ConcentrationMeasurement.ID] = { + [clusters.Pm10ConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.dustSensor.NAME, capabilities.dustSensor.dustLevel, fields.units.UGM3), + [clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.dustSensor.NAME), + [clusters.Pm10ConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.dustHealthConcern.dustHealthConcern), + }, + [clusters.Pm25ConcentrationMeasurement.ID] = { + [clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.fineDustSensor.NAME, capabilities.fineDustSensor.fineDustLevel, fields.units.UGM3), + [clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.fineDustSensor.NAME), + [clusters.Pm25ConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.fineDustHealthConcern.fineDustHealthConcern), + }, + [clusters.PressureMeasurement.ID] = { + [clusters.PressureMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.pressure_measured_value_handler + }, + [clusters.RadonConcentrationMeasurement.ID] = { + [clusters.RadonConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.radonMeasurement.NAME, capabilities.radonMeasurement.radonLevel, fields.units.PCIL), + [clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.radonMeasurement.NAME), + [clusters.RadonConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.radonHealthConcern.radonHealthConcern) + }, + [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.ID] = { + [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.tvocMeasurement.NAME, capabilities.tvocMeasurement.tvocLevel, fields.units.PPB), + [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.tvocMeasurement.NAME), + [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.tvocHealthConcern.tvocHealthConcern) + } + } + }, + can_handle = require("sub_drivers.air_quality_sensor.can_handle") +} + +return matter_air_quality_sensor_handler diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/bosch_button_contact/can_handle.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/bosch_button_contact/can_handle.lua new file mode 100644 index 0000000000..9c679f3607 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/bosch_button_contact/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_bosch_button_contact(opts, driver, device) + local device_lib = require "st.device" + local BOSCH_VENDOR_ID = 0x1209 + local BOSCH_PRODUCT_ID = 0x3015 + if device.network_type == device_lib.NETWORK_TYPE_MATTER and + device.manufacturer_info.vendor_id == BOSCH_VENDOR_ID and + device.manufacturer_info.product_id == BOSCH_PRODUCT_ID then + return true, require("sub_drivers.bosch_button_contact") + end + return false +end + +return is_bosch_button_contact diff --git a/drivers/SmartThings/matter-sensor/src/bosch-button-contact/init.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/bosch_button_contact/init.lua similarity index 85% rename from drivers/SmartThings/matter-sensor/src/bosch-button-contact/init.lua rename to drivers/SmartThings/matter-sensor/src/sub_drivers/bosch_button_contact/init.lua index 1dfd3960ce..cbbd71cf53 100644 --- a/drivers/SmartThings/matter-sensor/src/bosch-button-contact/init.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/bosch_button_contact/init.lua @@ -1,37 +1,13 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" -local device_lib = require "st.device" local lua_socket = require "socket" local log = require "log" local START_BUTTON_PRESS = "__start_button_press" -local BOSCH_VENDOR_ID = 0x1209 -local BOSCH_PRODUCT_ID = 0x3015 - -local function is_bosch_button_contact(opts, driver, device) - if device.network_type == device_lib.NETWORK_TYPE_MATTER and - device.manufacturer_info.vendor_id == BOSCH_VENDOR_ID and - device.manufacturer_info.product_id == BOSCH_PRODUCT_ID then - return true - end - return false -end - local function get_field_for_endpoint(device, field, endpoint) return device:get_field(string.format("%s_%d", field, endpoint)) end @@ -155,8 +131,8 @@ local Bosch_Button_Contact_Sensor = { [clusters.Switch.events.MultiPressComplete.ID] = multi_press_complete_event_handler } }, - }, - can_handle = is_bosch_button_contact, + }, + can_handle = require("sub_drivers.bosch_button_contact.can_handle"), } return Bosch_Button_Contact_Sensor diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/smoke_co_alarm/can_handle.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/smoke_co_alarm/can_handle.lua new file mode 100644 index 0000000000..31bd7e025b --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/smoke_co_alarm/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_matter_smoke_co_alarm(opts, driver, device) + local SMOKE_CO_ALARM_DEVICE_TYPE_ID = 0x0076 + 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 then + return true, require("sub_drivers.smoke_co_alarm") + end + end + end + + return false +end + +return is_matter_smoke_co_alarm diff --git a/drivers/SmartThings/matter-sensor/src/smoke-co-alarm/init.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/smoke_co_alarm/init.lua similarity index 61% rename from drivers/SmartThings/matter-sensor/src/smoke-co-alarm/init.lua rename to drivers/SmartThings/matter-sensor/src/sub_drivers/smoke_co_alarm/init.lua index c5fe00a4be..8f8653b936 100644 --- a/drivers/SmartThings/matter-sensor/src/smoke-co-alarm/init.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/smoke_co_alarm/init.lua @@ -1,59 +1,29 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" -local embedded_cluster_utils = require "embedded-cluster-utils" +local version = require "version" +local embedded_cluster_utils = require "sensor_utils.embedded_cluster_utils" +local sensor_utils = require "sensor_utils.utils" +local fields = require "sensor_utils.fields" -local CARBON_MONOXIDE_MEASUREMENT_UNIT = "CarbonMonoxideConcentrationMeasurement_unit" -local SMOKE_CO_ALARM_DEVICE_TYPE_ID = 0x0076 +if version.api < 10 then + clusters.CarbonMonoxideConcentrationMeasurement = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement" + clusters.SmokeCoAlarm = require "embedded_clusters.SmokeCoAlarm" +end -local HardwareFaultAlert = "__HardwareFaultAlert" -local BatteryAlert = "__BatteryAlert" -local BatteryLevel = "__BatteryLevel" -local battery_support = { - NO_BATTERY = "NO_BATTERY", - BATTERY_LEVEL = "BATTERY_LEVEL", - BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE" -} +-- SUBDRIVER UTILS -- -local version = require "version" -if version.api < 10 then - clusters.SmokeCoAlarm = require "SmokeCoAlarm" -end +local smoke_co_alarm_utils = {} -local function is_matter_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 then - return true - end - end - end +local CARBON_MONOXIDE_MEASUREMENT_UNIT = "CarbonMonoxideConcentrationMeasurement_unit" - return false -end +local HardwareFaultAlert = "__HardwareFaultAlert" +local BatteryAlert = "__BatteryAlert" +local BatteryLevel = "__BatteryLevel" -local tbl_contains = function(t, val) - for _, v in pairs(t) do - if v == val then - return true - end - end - return false -end local supported_profiles = { @@ -64,13 +34,14 @@ local supported_profiles = "co-comeas-colevel-battery", "smoke", "smoke-battery", + "smoke-temp-humidity-battery", "smoke-co-comeas", "smoke-co-comeas-battery", "smoke-co-temp-humidity-comeas", "smoke-co-temp-humidity-comeas-battery" } -local function match_profile(device, battery_supported) +function smoke_co_alarm_utils.match_profile(device, battery_supported) local smoke_eps = embedded_cluster_utils.get_endpoints(device, clusters.SmokeCoAlarm.ID, {feature_bitmap = clusters.SmokeCoAlarm.types.Feature.SMOKE_ALARM}) local co_eps = embedded_cluster_utils.get_endpoints(device, clusters.SmokeCoAlarm.ID, {feature_bitmap = clusters.SmokeCoAlarm.types.Feature.CO_ALARM}) local temp_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureMeasurement.ID) @@ -99,14 +70,14 @@ local function match_profile(device, battery_supported) if #co_level_eps > 0 then profile_name = profile_name .. "-colevel" end - if battery_supported == battery_support.BATTERY_PERCENTAGE then + if battery_supported == fields.battery_support.BATTERY_PERCENTAGE then profile_name = profile_name .. "-battery" end -- remove leading "-" profile_name = string.sub(profile_name, 2) - if tbl_contains(supported_profiles, profile_name) then + if sensor_utils.tbl_contains(supported_profiles, profile_name) then device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) else device.log.warn_with({hub_logs=true}, string.format("No matching profile for device. Tried to use profile %s.", profile_name)) @@ -123,11 +94,25 @@ local function match_profile(device, battery_supported) device:try_update_metadata({profile = profile_name}) end -local function device_init(driver, device) + +-- SMOKE CO ALARM LIFECYCLE HANDLERS -- + +local SmokeLifeycleHandlers = {} + +function SmokeLifeycleHandlers.device_init(driver, device) device:subscribe() end -local function info_changed(self, device, event, args) +function SmokeLifeycleHandlers.do_configure(driver, device) + local battery_feature_eps = device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) + if #battery_feature_eps > 0 then + device:send(clusters.PowerSource.attributes.AttributeList:read()) + else + smoke_co_alarm_utils.match_profile(device, fields.battery_support.NO_BATTERY) + end +end + +function SmokeLifeycleHandlers.info_changed(self, device, event, args) if device.preferences then if device.preferences["certifiedpreferences.smokeSensorSensitivity"] ~= args.old_st_store.preferences["certifiedpreferences.smokeSensorSensitivity"] then local eps = embedded_cluster_utils.get_endpoints(device, clusters.SmokeCoAlarm.ID) @@ -150,8 +135,12 @@ local function info_changed(self, device, event, args) end end --- Matter Handlers -- -local function binary_state_handler_factory(zeroEvent, nonZeroEvent) + +-- CLUSTER ATTRIBUTE HANDLERS -- + +local sub_driver_handlers = {} + +function sub_driver_handlers.smoke_co_alarm_state_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) @@ -161,7 +150,7 @@ local function binary_state_handler_factory(zeroEvent, nonZeroEvent) end end -local function test_in_progress_event_handler(driver, device, ib, response) +function sub_driver_handlers.test_in_progress_handler(driver, device, ib, response) if device:supports_capability(capabilities.smokeDetector) then if ib.data.value then device:emit_event_for_endpoint(ib.endpoint_id, capabilities.smokeDetector.smoke.tested()) @@ -178,7 +167,7 @@ local function test_in_progress_event_handler(driver, device, ib, response) end end -local function carbon_monoxide_attr_handler(driver, device, ib, response) +function sub_driver_handlers.carbon_monoxide_measured_value_handler(driver, device, ib, response) local value = ib.data.value local unit = device:get_field(CARBON_MONOXIDE_MEASUREMENT_UNIT) if unit == clusters.CarbonMonoxideConcentrationMeasurement.types.MeasurementUnitEnum.PPB then @@ -190,12 +179,12 @@ local function carbon_monoxide_attr_handler(driver, device, ib, response) device:emit_event_for_endpoint(ib.endpoint_id, capabilities.carbonMonoxideMeasurement.carbonMonoxideLevel({value = value, unit = "ppm"})) end -local function carbon_monoxide_unit_attr_handler(driver, device, ib, response) +function sub_driver_handlers.carbon_monoxide_measurement_unit_handler(driver, device, ib, response) local unit = ib.data.value device:set_field(CARBON_MONOXIDE_MEASUREMENT_UNIT, unit, { persist = true }) end -local function hardware_fault_capability_handler(device) +function sub_driver_handlers.hardware_fault_capability_handler(device) local batLevel, batAlert = device:get_field(BatteryLevel), device:get_field(BatteryAlert) if device:get_field(HardwareFaultAlert) == true or (batLevel and batAlert and (batAlert > batLevel)) then device:emit_event(capabilities.hardwareFault.hardwareFault.detected()) @@ -204,31 +193,31 @@ local function hardware_fault_capability_handler(device) end end -local function hardware_fault_alert_handler(driver, device, ib, response) +function sub_driver_handlers.hardware_fault_alert_handler(driver, device, ib, response) device:set_field(HardwareFaultAlert, ib.data.value, {persist = true}) - hardware_fault_capability_handler(device) + sub_driver_handlers.hardware_fault_capability_handler(device) end -local function battery_alert_attr_handler(driver, device, ib, response) +function sub_driver_handlers.battery_alert_handler(driver, device, ib, response) device:set_field(BatteryAlert, ib.data.value, {persist = true}) - hardware_fault_capability_handler(device) + sub_driver_handlers.hardware_fault_capability_handler(device) end -local function power_source_attribute_list_handler(driver, device, ib, response) +function sub_driver_handlers.power_source_attribute_list_handler(driver, device, ib, response) for _, attr in ipairs(ib.data.elements) do -- Re-profile the device if BatPercentRemaining (Attribute ID 0x0C) or -- BatChargeLevel (Attribute ID 0x0E) is present. if attr.value == 0x0C then - match_profile(device, battery_support.BATTERY_PERCENTAGE) + smoke_co_alarm_utils.match_profile(device, fields.battery_support.BATTERY_PERCENTAGE) return elseif attr.value == 0x0E then - match_profile(device, battery_support.BATTERY_LEVEL) + smoke_co_alarm_utils.match_profile(device, fields.battery_support.BATTERY_LEVEL) return end end end -local function handle_battery_charge_level(driver, device, ib, response) +function sub_driver_handlers.bat_charge_level_handler(driver, device, ib, response) device:set_field(BatteryLevel, ib.data.value, {persist = true}) -- value used in hardware_fault_capability_handler if device:supports_capability(capabilities.batteryLevel) then -- check required since attribute is subscribed to even without batteryLevel support, to set the field above if ib.data.value == clusters.PowerSource.types.BatChargeLevelEnum.OK then @@ -241,80 +230,36 @@ local function handle_battery_charge_level(driver, device, ib, response) end end -local function handle_battery_percent_remaining(driver, device, ib, response) - if ib.data.value ~= nil then - device:emit_event(capabilities.battery.battery(math.floor(ib.data.value / 2.0 + 0.5))) - end -end -local function do_configure(driver, device) - local battery_feature_eps = device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) - if #battery_feature_eps > 0 then - device:send(clusters.PowerSource.attributes.AttributeList:read()) - else - match_profile(device, battery_support.NO_BATTERY) - end -end +-- SUBDRIVER TEMPLATE -- local matter_smoke_co_alarm_handler = { NAME = "matter-smoke-co-alarm", lifecycle_handlers = { - init = device_init, - infoChanged = info_changed, - doConfigure = do_configure + init = SmokeLifeycleHandlers.device_init, + infoChanged = SmokeLifeycleHandlers.info_changed, + doConfigure = SmokeLifeycleHandlers.do_configure }, matter_handlers = { attr = { - [clusters.SmokeCoAlarm.ID] = { - [clusters.SmokeCoAlarm.attributes.SmokeState.ID] = binary_state_handler_factory(capabilities.smokeDetector.smoke.clear(), capabilities.smokeDetector.smoke.detected()), - [clusters.SmokeCoAlarm.attributes.COState.ID] = binary_state_handler_factory(capabilities.carbonMonoxideDetector.carbonMonoxide.clear(), capabilities.carbonMonoxideDetector.carbonMonoxide.detected()), - [clusters.SmokeCoAlarm.attributes.BatteryAlert.ID] = battery_alert_attr_handler, - [clusters.SmokeCoAlarm.attributes.TestInProgress.ID] = test_in_progress_event_handler, - [clusters.SmokeCoAlarm.attributes.HardwareFaultAlert.ID] = hardware_fault_alert_handler, - }, [clusters.CarbonMonoxideConcentrationMeasurement.ID] = { - [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue.ID] = carbon_monoxide_attr_handler, - [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = carbon_monoxide_unit_attr_handler, + [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue.ID] = sub_driver_handlers.carbon_monoxide_measured_value_handler, + [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = sub_driver_handlers.carbon_monoxide_measurement_unit_handler, }, [clusters.PowerSource.ID] = { - [clusters.PowerSource.attributes.AttributeList.ID] = power_source_attribute_list_handler, - [clusters.PowerSource.attributes.BatPercentRemaining.ID] = handle_battery_percent_remaining, - [clusters.PowerSource.attributes.BatChargeLevel.ID] = handle_battery_charge_level, + [clusters.PowerSource.attributes.AttributeList.ID] = sub_driver_handlers.power_source_attribute_list_handler, + [clusters.PowerSource.attributes.BatChargeLevel.ID] = sub_driver_handlers.bat_charge_level_handler, + }, + [clusters.SmokeCoAlarm.ID] = { + [clusters.SmokeCoAlarm.attributes.BatteryAlert.ID] = sub_driver_handlers.battery_alert_handler, + [clusters.SmokeCoAlarm.attributes.COState.ID] = sub_driver_handlers.smoke_co_alarm_state_factory(capabilities.carbonMonoxideDetector.carbonMonoxide.clear(), capabilities.carbonMonoxideDetector.carbonMonoxide.detected()), + [clusters.SmokeCoAlarm.attributes.HardwareFaultAlert.ID] = sub_driver_handlers.hardware_fault_alert_handler, + [clusters.SmokeCoAlarm.attributes.SmokeState.ID] = sub_driver_handlers.smoke_co_alarm_state_factory(capabilities.smokeDetector.smoke.clear(), capabilities.smokeDetector.smoke.detected()), + [clusters.SmokeCoAlarm.attributes.TestInProgress.ID] = sub_driver_handlers.test_in_progress_handler, }, }, }, - subscribed_attributes = { - [capabilities.smokeDetector.ID] = { - clusters.SmokeCoAlarm.attributes.SmokeState, - clusters.SmokeCoAlarm.attributes.TestInProgress, - }, - [capabilities.carbonMonoxideDetector.ID] = { - clusters.SmokeCoAlarm.attributes.COState, - clusters.SmokeCoAlarm.attributes.TestInProgress, - }, - [capabilities.hardwareFault.ID] = { - clusters.SmokeCoAlarm.attributes.HardwareFaultAlert, - clusters.SmokeCoAlarm.attributes.BatteryAlert, - clusters.PowerSource.attributes.BatChargeLevel, - }, - [capabilities.temperatureMeasurement.ID] = { - clusters.TemperatureMeasurement.attributes.MeasuredValue - }, - [capabilities.relativeHumidityMeasurement.ID] = { - clusters.RelativeHumidityMeasurement.attributes.MeasuredValue - }, - [capabilities.carbonMonoxideMeasurement.ID] = { - clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue, - clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.batteryLevel.ID] = { - clusters.PowerSource.attributes.BatChargeLevel, - }, - [capabilities.battery.ID] = { - clusters.PowerSource.attributes.BatPercentRemaining, - } - }, - can_handle = is_matter_smoke_co_alarm + can_handle = require("sub_drivers.smoke_co_alarm.can_handle") } -return matter_smoke_co_alarm_handler \ No newline at end of file +return matter_smoke_co_alarm_handler diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor.lua index 871aec64e2..1988563bc8 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" @@ -19,17 +8,23 @@ local SinglePrecisionFloat = require "st.matter.data_types.SinglePrecisionFloat" local clusters = require "st.matter.clusters" -clusters.AirQuality = require "AirQuality" -clusters.CarbonMonoxideConcentrationMeasurement = require "CarbonMonoxideConcentrationMeasurement" -clusters.CarbonDioxideConcentrationMeasurement = require "CarbonDioxideConcentrationMeasurement" -clusters.FormaldehydeConcentrationMeasurement = require "FormaldehydeConcentrationMeasurement" -clusters.NitrogenDioxideConcentrationMeasurement = require "NitrogenDioxideConcentrationMeasurement" -clusters.OzoneConcentrationMeasurement = require "OzoneConcentrationMeasurement" -clusters.Pm1ConcentrationMeasurement = require "Pm1ConcentrationMeasurement" -clusters.Pm10ConcentrationMeasurement = require "Pm10ConcentrationMeasurement" -clusters.Pm25ConcentrationMeasurement = require "Pm25ConcentrationMeasurement" -clusters.RadonConcentrationMeasurement = require "RadonConcentrationMeasurement" -clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "TotalVolatileOrganicCompoundsConcentrationMeasurement" +local version = require "version" + +-- Include driver-side definitions when lua libs api version is < 10 +if version.api < 10 then + clusters.AirQuality = require "embedded_clusters.AirQuality" + clusters.CarbonMonoxideConcentrationMeasurement = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement" + clusters.CarbonDioxideConcentrationMeasurement = require "embedded_clusters.CarbonDioxideConcentrationMeasurement" + clusters.FormaldehydeConcentrationMeasurement = require "embedded_clusters.FormaldehydeConcentrationMeasurement" + clusters.NitrogenDioxideConcentrationMeasurement = require "embedded_clusters.NitrogenDioxideConcentrationMeasurement" + clusters.OzoneConcentrationMeasurement = require "embedded_clusters.OzoneConcentrationMeasurement" + clusters.Pm1ConcentrationMeasurement = require "embedded_clusters.Pm1ConcentrationMeasurement" + clusters.Pm10ConcentrationMeasurement = require "embedded_clusters.Pm10ConcentrationMeasurement" + clusters.Pm25ConcentrationMeasurement = require "embedded_clusters.Pm25ConcentrationMeasurement" + clusters.RadonConcentrationMeasurement = require "embedded_clusters.RadonConcentrationMeasurement" + clusters.SmokeCoAlarm = require "embedded_clusters.SmokeCoAlarm" + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement" +end local mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("aqs-temp-humidity-all-level-all-meas.yml"), @@ -145,7 +140,7 @@ local mock_device_level = test.mock_device.build_test_matter_device({ }) local mock_device_co = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("aqs-temp-humidity-all-level-all-meas.yml"), + profile = t_utils.get_profile_definition("aqs.yml"), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -174,7 +169,7 @@ local mock_device_co = test.mock_device.build_test_matter_device({ }) local mock_device_co2 = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("aqs-temp-humidity-all-level-all-meas.yml"), + profile = t_utils.get_profile_definition("aqs.yml"), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -234,7 +229,12 @@ local mock_device_tvoc = test.mock_device.build_test_matter_device({ }) -- create test_init functions -local function initialize_mock_device(generic_mock_device, generic_subscribed_attributes) +local function initialize_mock_device(generic_mock_device, generic_subscribed_attributes, expected_supported_values_setters) + test.mock_device.add_test_device(generic_mock_device) + test.socket.capability:__expect_send(generic_mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "unhealthy", "moderate", "slightlyUnhealthy",}, {visibility={displayed=false}}))) + if expected_supported_values_setters ~= nil then + expected_supported_values_setters() + end local subscribe_request = nil for _, attributes in pairs(generic_subscribed_attributes) do for _, attribute in ipairs(attributes) do @@ -246,7 +246,6 @@ local function initialize_mock_device(generic_mock_device, generic_subscribed_at end end test.socket.matter:__expect_send({generic_mock_device.id, subscribe_request}) - test.mock_device.add_test_device(generic_mock_device) end -- TODO add tests for configuration using modular profiles @@ -334,7 +333,19 @@ local function test_init() clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue, }, } - initialize_mock_device(mock_device, subscribed_attributes) + local expected_supported_values_setters = function() + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.nitrogenDioxideHealthConcern.supportedNitrogenDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.ozoneHealthConcern.supportedOzoneValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.formaldehydeHealthConcern.supportedFormaldehydeValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.veryFineDustHealthConcern.supportedVeryFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fineDustHealthConcern.supportedFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.dustHealthConcern.supportedDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.radonHealthConcern.supportedRadonValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tvocHealthConcern.supportedTvocValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + end + initialize_mock_device(mock_device, subscribed_attributes, expected_supported_values_setters) end test.set_test_init_function(test_init) @@ -411,7 +422,19 @@ local function test_init_level() clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue, } } - initialize_mock_device(mock_device_level, subscribed_attributes) + local expected_supported_values_setters = function() + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.nitrogenDioxideHealthConcern.supportedNitrogenDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.ozoneHealthConcern.supportedOzoneValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.formaldehydeHealthConcern.supportedFormaldehydeValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.veryFineDustHealthConcern.supportedVeryFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.fineDustHealthConcern.supportedFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.dustHealthConcern.supportedDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.radonHealthConcern.supportedRadonValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.tvocHealthConcern.supportedTvocValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + end + initialize_mock_device(mock_device_level, subscribed_attributes, expected_supported_values_setters) end local function test_init_tvoc() @@ -440,26 +463,12 @@ local function test_init_co_co2() [capabilities.airQualityHealthConcern.ID] = { clusters.AirQuality.attributes.AirQuality }, - [capabilities.carbonMonoxideMeasurement.ID] = { - clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue, - clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.carbonMonoxideHealthConcern.ID] = { - clusters.CarbonMonoxideConcentrationMeasurement.attributes.LevelValue, - }, } local attr_co2 = { [capabilities.airQualityHealthConcern.ID] = { clusters.AirQuality.attributes.AirQuality - }, - [capabilities.carbonDioxideMeasurement.ID] = { - clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasuredValue, - clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.carbonDioxideHealthConcern.ID] = { - clusters.CarbonDioxideConcentrationMeasurement.attributes.LevelValue, - }, + } } initialize_mock_device(mock_device_co, attr_co) @@ -468,7 +477,7 @@ end -- run the profile configuration tests -local function test_aqs_device_type_do_configure(generic_mock_device, expected_profile, expected_supported_values_setters) +local function test_aqs_device_type_do_configure(generic_mock_device, expected_profile) test.socket.device_lifecycle:__queue_receive({generic_mock_device.id, "doConfigure"}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit:read()}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit:read()}) @@ -480,10 +489,6 @@ local function test_aqs_device_type_do_configure(generic_mock_device, expected_p test.socket.matter:__expect_send({generic_mock_device.id, clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit:read()}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit:read()}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit:read()}) - test.socket.capability:__expect_send(generic_mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, {visibility={displayed=false}}))) - if expected_supported_values_setters ~= nil then - expected_supported_values_setters() - end generic_mock_device:expect_metadata_update({ profile = expected_profile }) generic_mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -491,19 +496,7 @@ end test.register_coroutine_test( "Configure should read units from device and profile change as needed", function() - local expected_supported_values_setters = function() - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.nitrogenDioxideHealthConcern.supportedNitrogenDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.ozoneHealthConcern.supportedOzoneValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.formaldehydeHealthConcern.supportedFormaldehydeValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.veryFineDustHealthConcern.supportedVeryFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fineDustHealthConcern.supportedFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.dustHealthConcern.supportedDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.radonHealthConcern.supportedRadonValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tvocHealthConcern.supportedTvocValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - end - test_aqs_device_type_do_configure(mock_device, "aqs-temp-humidity-all-level-all-meas", expected_supported_values_setters) + test_aqs_device_type_do_configure(mock_device, "aqs-temp-humidity-all-level-all-meas") end ) @@ -518,19 +511,7 @@ test.register_coroutine_test( test.register_coroutine_test( "Configure should read units from device and profile change as needed", function() - local expected_supported_values_setters = function() - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.nitrogenDioxideHealthConcern.supportedNitrogenDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.ozoneHealthConcern.supportedOzoneValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.formaldehydeHealthConcern.supportedFormaldehydeValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.veryFineDustHealthConcern.supportedVeryFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.fineDustHealthConcern.supportedFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.dustHealthConcern.supportedDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.radonHealthConcern.supportedRadonValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.tvocHealthConcern.supportedTvocValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - end - test_aqs_device_type_do_configure(mock_device_level, "aqs-temp-humidity-all-level", expected_supported_values_setters) + test_aqs_device_type_do_configure(mock_device_level, "aqs-temp-humidity-all-level") end, { test_init = test_init_level } ) @@ -538,14 +519,8 @@ test.register_coroutine_test( test.register_coroutine_test( "Configure should not catch co2, only co in the first check", function() - local expected_supported_co_values = function() - test.socket.capability:__expect_send(mock_device_co:generate_test_message("main", capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - end - local expected_supported_co2_values = function() - test.socket.capability:__expect_send(mock_device_co2:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - end - test_aqs_device_type_do_configure(mock_device_co, "aqs-temp-humidity-all-meas", expected_supported_co_values) - test_aqs_device_type_do_configure(mock_device_co2, "aqs-temp-humidity-co2-pm25-tvoc-meas", expected_supported_co2_values) + test_aqs_device_type_do_configure(mock_device_co, "aqs-temp-humidity-all-meas") + test_aqs_device_type_do_configure(mock_device_co2, "aqs-temp-humidity-co2-pm25-tvoc-meas") end, { test_init = test_init_co_co2 } ) diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor_modular.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor_modular.lua index d799c31b64..5b6b43f2f5 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor_modular.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor_modular.lua @@ -1,29 +1,16 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" -local utils = require "st.utils" -local dkjson = require "dkjson" local clusters = require "st.matter.clusters" -test.set_rpc_version(8) +test.disable_startup_messages() -local mock_device = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("aqs-temp-humidity-all-level-all-meas.yml"), +local mock_device_all = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("aqs.yml"), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -41,7 +28,7 @@ local mock_device = test.mock_device.build_test_matter_device({ { endpoint_id = 1, clusters = { - {cluster_id = clusters.AirQuality.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.AirQuality.ID, cluster_type = "SERVER", feature_map = 3}, {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.CarbonMonoxideConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 3}, @@ -63,7 +50,7 @@ local mock_device = test.mock_device.build_test_matter_device({ }) local mock_device_common = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("aqs-temp-humidity-co2-pm25-tvoc-meas.yml"), + profile = t_utils.get_profile_definition("aqs.yml"), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -81,7 +68,7 @@ local mock_device_common = test.mock_device.build_test_matter_device({ { endpoint_id = 1, clusters = { - {cluster_id = clusters.AirQuality.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.AirQuality.ID, cluster_type = "SERVER", feature_map = 3}, {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.CarbonDioxideConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 1}, @@ -95,25 +82,34 @@ local mock_device_common = test.mock_device.build_test_matter_device({ } }) --- create test_init functions -local function initialize_mock_device(generic_mock_device, generic_subscribed_attributes) - local subscribe_request = nil - for _, attributes in pairs(generic_subscribed_attributes) do - for _, attribute in ipairs(attributes) do - if subscribe_request == nil then - subscribe_request = attribute:subscribe(generic_mock_device) - else - subscribe_request:merge(attribute:subscribe(generic_mock_device)) - end - end - end - test.socket.matter:__expect_send({generic_mock_device.id, subscribe_request}) - test.mock_device.add_test_device(generic_mock_device) - return subscribe_request +local function test_init_all() + test.mock_device.add_test_device(mock_device_all) + test.socket.device_lifecycle:__queue_receive({ mock_device_all.id, "init" }) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", + capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "unhealthy", "moderate", "slightlyUnhealthy"}, + {visibility={displayed=false}})) + ) + -- on device create, a generic AQS device will be profiled as aqs, thus only subscribing to one attribute + local subscribe_request = clusters.AirQuality.attributes.AirQuality:subscribe(mock_device_all) + test.socket.matter:__expect_send({mock_device_all.id, subscribe_request}) +end + +local function test_init_common() + test.mock_device.add_test_device(mock_device_common) + test.socket.device_lifecycle:__queue_receive({ mock_device_common.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_common.id, "init" }) + test.socket.capability:__expect_send(mock_device_common:generate_test_message("main", + capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "unhealthy", "moderate", "slightlyUnhealthy"}, + {visibility={displayed=false}})) + ) + -- on device create, a generic AQS device will be profiled as aqs, thus only subscribing to one attribute + local subscribe_request = clusters.AirQuality.attributes.AirQuality:subscribe(mock_device_common) + test.socket.matter:__expect_send({mock_device_common.id, subscribe_request}) end -local subscribe_request_all -local function test_init() +test.set_test_init_function(test_init_all) + +local function get_subscribe_request_all() local subscribed_attributes = { [capabilities.relativeHumidityMeasurement.ID] = { clusters.RelativeHumidityMeasurement.attributes.MeasuredValue @@ -195,11 +191,20 @@ local function test_init() clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue, }, } - subscribe_request_all = initialize_mock_device(mock_device, subscribed_attributes) + local subscribe_request = nil + for _, attributes in pairs(subscribed_attributes) do + for _, attribute in pairs(attributes) do + if subscribe_request == nil then + subscribe_request = attribute:subscribe(mock_device_all) + else + subscribe_request:merge(attribute:subscribe(mock_device_all)) + end + end + end + return subscribe_request end -local subscribe_request_common -local function test_init_common() +local function get_subscribe_request_common() local subscribed_attributes = { [capabilities.relativeHumidityMeasurement.ID] = { clusters.RelativeHumidityMeasurement.attributes.MeasuredValue @@ -225,12 +230,21 @@ local function test_init_common() clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit, }, } - subscribe_request_common = initialize_mock_device(mock_device_common, subscribed_attributes) + local subscribe_request = nil + for _, attributes in pairs(subscribed_attributes) do + for _, attribute in pairs(attributes) do + if subscribe_request == nil then + subscribe_request = attribute:subscribe(mock_device_common) + else + subscribe_request:merge(attribute:subscribe(mock_device_common)) + end + end + end + return subscribe_request end -test.set_test_init_function(test_init) -- run the profile configuration tests -local function test_aqs_device_type_update_modular_profile(generic_mock_device, expected_metadata, subscribe_request) +local function test_aqs_device_type_update_modular_profile(generic_mock_device, expected_metadata, subscribe_request, expected_supported_values_setters) test.socket.device_lifecycle:__queue_receive({generic_mock_device.id, "doConfigure"}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit:read()}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit:read()}) @@ -244,73 +258,93 @@ local function test_aqs_device_type_update_modular_profile(generic_mock_device, test.socket.matter:__expect_send({generic_mock_device.id, clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit:read()}) generic_mock_device:expect_metadata_update(expected_metadata) generic_mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - local device_info_copy = utils.deep_copy(generic_mock_device.raw_st_data) - device_info_copy.profile.id = "aqs-modular" - local device_info_json = dkjson.encode(device_info_copy) - test.socket.device_lifecycle:__queue_receive({ generic_mock_device.id, "infoChanged", device_info_json }) + local updated_device_profile = t_utils.get_profile_definition("aqs-modular.yml", + {enabled_optional_capabilities = expected_metadata.optional_component_capabilities} + ) + test.socket.device_lifecycle:__queue_receive(generic_mock_device:generate_info_changed({ profile = updated_device_profile })) + if expected_supported_values_setters ~= nil then + expected_supported_values_setters() + end test.socket.matter:__expect_send({generic_mock_device.id, subscribe_request}) end -local expected_metadata_all = { - optional_component_capabilities={ - { - "main", - { - "temperatureMeasurement", - "relativeHumidityMeasurement", - "carbonMonoxideMeasurement", - "carbonDioxideMeasurement", - "nitrogenDioxideMeasurement", - "ozoneMeasurement", - "formaldehydeMeasurement", - "veryFineDustSensor", - "fineDustSensor", - "dustSensor", - "radonMeasurement", - "tvocMeasurement", - "carbonMonoxideHealthConcern", - "carbonDioxideHealthConcern", - "nitrogenDioxideHealthConcern", - "ozoneHealthConcern", - "formaldehydeHealthConcern", - "veryFineDustHealthConcern", - "fineDustHealthConcern", - "dustHealthConcern", - "radonHealthConcern", - "tvocHealthConcern", - }, - }, - }, - profile="aqs-modular-temp-humidity", -} - test.register_coroutine_test( "Device with modular profile should enable correct optional capabilities - all clusters", function() - test_aqs_device_type_update_modular_profile(mock_device, expected_metadata_all, subscribe_request_all) - end -) - -local expected_metadata_common = { - optional_component_capabilities={ - { - "main", - { - "temperatureMeasurement", - "relativeHumidityMeasurement", - "carbonDioxideMeasurement", - "fineDustSensor", - "tvocMeasurement", + local expected_metadata_all = { + optional_component_capabilities={ + { + "main", + { + "temperatureMeasurement", + "relativeHumidityMeasurement", + "carbonMonoxideMeasurement", + "carbonDioxideMeasurement", + "nitrogenDioxideMeasurement", + "ozoneMeasurement", + "formaldehydeMeasurement", + "veryFineDustSensor", + "fineDustSensor", + "dustSensor", + "radonMeasurement", + "tvocMeasurement", + "carbonMonoxideHealthConcern", + "carbonDioxideHealthConcern", + "nitrogenDioxideHealthConcern", + "ozoneHealthConcern", + "formaldehydeHealthConcern", + "veryFineDustHealthConcern", + "fineDustHealthConcern", + "dustHealthConcern", + "radonHealthConcern", + "tvocHealthConcern", + }, + }, }, - }, - }, - profile="aqs-modular-temp-humidity", -} + profile="aqs-modular-temp-humidity", + } + local expected_supported_values_setters = function() + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "unhealthy", "moderate", "slightlyUnhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.nitrogenDioxideHealthConcern.supportedNitrogenDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.ozoneHealthConcern.supportedOzoneValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.formaldehydeHealthConcern.supportedFormaldehydeValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.veryFineDustHealthConcern.supportedVeryFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.fineDustHealthConcern.supportedFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.dustHealthConcern.supportedDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.radonHealthConcern.supportedRadonValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.tvocHealthConcern.supportedTvocValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + end + local subscribe_request_all = get_subscribe_request_all() + test_aqs_device_type_update_modular_profile(mock_device_all, expected_metadata_all, subscribe_request_all, expected_supported_values_setters) + end, + { test_init = test_init_all } +) test.register_coroutine_test( "Device with modular profile should enable correct optional capabilities - common clusters", function() - test_aqs_device_type_update_modular_profile(mock_device_common, expected_metadata_common, subscribe_request_common) + local expected_metadata_common = { + optional_component_capabilities={ + { + "main", + { + "temperatureMeasurement", + "relativeHumidityMeasurement", + "carbonDioxideMeasurement", + "fineDustSensor", + "tvocMeasurement", + }, + }, + }, + profile="aqs-modular-temp-humidity", + } + local expected_supported_values_setters = function() + test.socket.capability:__expect_send(mock_device_common:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "unhealthy", "moderate", "slightlyUnhealthy"}, {visibility={displayed=false}}))) + end + local subscribe_request_common = get_subscribe_request_common() + test_aqs_device_type_update_modular_profile(mock_device_common, expected_metadata_common, subscribe_request_common, expected_supported_values_setters) end, { test_init = test_init_common } ) diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_bosch_button_contact.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_bosch_button_contact.lua index b63abcea14..1ca2add2ff 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_bosch_button_contact.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_bosch_button_contact.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- package.path = package.path .. ";./?lua" -- package.loaded["path"] = dofile("mock_path.lua") local test = require "integration_test" diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_flow_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_flow_sensor.lua index f6312dfc8f..6b1301cb96 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_flow_sensor.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_flow_sensor.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_freeze_leak_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_freeze_leak_sensor.lua index 7aae047a29..44a984b2a7 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_freeze_leak_sensor.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_freeze_leak_sensor.lua @@ -1,22 +1,15 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" -clusters.BooleanStateConfiguration = require "BooleanStateConfiguration" +local version = require "version" + +if version.api < 11 then + clusters.BooleanStateConfiguration = require "embedded_clusters.BooleanStateConfiguration" +end local mock_device_freeze_leak = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("freeze-leak-fault-freezeSensitivity-leakSensitivity.yml"), diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_pressure_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_pressure_sensor.lua index 68134ed677..f7b3d7afe0 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_pressure_sensor.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_pressure_sensor.lua @@ -1,22 +1,15 @@ --- Copyright 2022 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" -local PressureMeasurementCluster = require "PressureMeasurement" + +if not pcall(function(cluster) return clusters[cluster] end, + "PressureMeasurement") then + clusters.PressureMeasurement = require "embedded_clusters.PressureMeasurement" +end --Note all endpoints are being mapped to the main component -- in the matter-sensor driver. If any devices require invoke/write @@ -35,7 +28,7 @@ local matter_endpoints = { { endpoint_id = 1, clusters = { - {cluster_id = PressureMeasurementCluster.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.PressureMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER"}, }, device_types = { @@ -51,7 +44,7 @@ local mock_device = test.mock_device.build_test_matter_device({ local function test_init() test.mock_device.add_test_device(mock_device) - local subscribe_request = PressureMeasurementCluster.attributes.MeasuredValue:subscribe(mock_device) + local subscribe_request = clusters.PressureMeasurement.attributes.MeasuredValue:subscribe(mock_device) subscribe_request:merge(clusters.PowerSource.attributes.BatPercentRemaining:subscribe(mock_device)) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) end @@ -65,7 +58,7 @@ test.register_message_test( direction = "receive", message = { mock_device.id, - PressureMeasurementCluster.server.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 1054) + clusters.PressureMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 1054) } }, { @@ -78,7 +71,7 @@ test.register_message_test( direction = "receive", message = { mock_device.id, - PressureMeasurementCluster.server.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 1055) + clusters.PressureMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 1055) } }, { @@ -91,7 +84,7 @@ test.register_message_test( direction = "receive", message = { mock_device.id, - PressureMeasurementCluster.server.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 0) + clusters.PressureMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 0) } }, { @@ -124,7 +117,7 @@ test.register_message_test( local function refresh_commands(dev) local req = clusters.PowerSource.attributes.BatPercentRemaining:read(dev) - req:merge(PressureMeasurementCluster.attributes.MeasuredValue:read(dev)) + req:merge(clusters.PressureMeasurement.attributes.MeasuredValue:read(dev)) return req end diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_rain_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_rain_sensor.lua index 835de811ae..21f8a0fa51 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_rain_sensor.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_rain_sensor.lua @@ -1,24 +1,16 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" +local version = require "version" -clusters.BooleanStateConfiguration = require "BooleanStateConfiguration" +if version.api < 11 then + clusters.BooleanStateConfiguration = require "embedded_clusters.BooleanStateConfiguration" +end local mock_device_rain = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("rain-fault.yml"), diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor.lua index 7116a03b92..a66e575310 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_battery.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_battery.lua index 72484efffe..8d3962de66 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_battery.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_battery.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" 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 index ed9718b947..b1e5d39248 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_featuremap.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_featuremap.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_rpc.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_rpc.lua index 7eb31d5c29..88af036e19 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_rpc.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_rpc.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_smoke_co_alarm.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_smoke_co_alarm.lua index 86060ab442..c7cfe26c2a 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_smoke_co_alarm.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_smoke_co_alarm.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" @@ -18,11 +7,10 @@ local t_utils = require "integration_test.utils" local SinglePrecisionFloat = require "st.matter.data_types.SinglePrecisionFloat" 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" + clusters.SmokeCoAlarm = require "embedded_clusters.SmokeCoAlarm" + clusters.CarbonMonoxideConcentrationMeasurement = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement" end local mock_device = test.mock_device.build_test_matter_device({ diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_smoke_co_alarm_battery.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_smoke_co_alarm_battery.lua index b618b3e5d6..816552935e 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_smoke_co_alarm_battery.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_smoke_co_alarm_battery.lua @@ -1,28 +1,16 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local uint32 = require "st.matter.data_types.Uint32" - 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" + clusters.SmokeCoAlarm = require "embedded_clusters.SmokeCoAlarm" + clusters.CarbonMonoxideConcentrationMeasurement = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement" end local mock_device = test.mock_device.build_test_matter_device({ @@ -69,6 +57,8 @@ local cluster_subscribe_list = { clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue, clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit, clusters.PowerSource.attributes.BatPercentRemaining, + clusters.PowerSource.attributes.BatChargeLevel, + clusters.SmokeCoAlarm.attributes.BatteryAlert, } local function test_init() diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 4654f4a84f..0871a9c0c5 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -25,6 +25,31 @@ matterManufacturer: vendorId: 0x115F productId: 0x2004 deviceProfileName: 3-button-battery-temperature-humidity + - id: "4447/4105" + deviceLabel: Aqara Light Switch H2 EU (4 Buttons 2 Channel) + vendorId: 0x115F + productId: 0x1009 + deviceProfileName: 4-button + - id: "4447/4104" + deviceLabel: Aqara Light Switch H2 EU (2 Buttons 1 Channel) + vendorId: 0x115F + productId: 0x1008 + deviceProfileName: 2-button + - id: "4447/4099" + deviceLabel: Aqara Light Switch H2 US (2 Buttons 1 Channel) + vendorId: 0x115F + productId: 0x1003 + deviceProfileName: 2-button + - id: "4447/4100" + deviceLabel: Aqara Light Switch H2 US (2 Buttons 2 Channel) + vendorId: 0x115F + productId: 0x1004 + deviceProfileName: 2-button + - id: "4447/4101" + deviceLabel: Aqara Light Switch H2 US (4 Buttons 3 Channel) + vendorId: 0x115F + productId: 0x1005 + deviceProfileName: 4-button #AiDot - id: "Linkind/Smart/Light/Bulb/A19/RGBTW" deviceLabel: Linkind Smart Light Bulb A19 RGBTW @@ -131,6 +156,26 @@ matterManufacturer: vendorId: 0x1396 productId: 0x1001 deviceProfileName: light-color-level-fan + - id: "5014/4214" + deviceLabel: Linkind Smart Light Bulb + vendorId: 0x1396 + productId: 0x1076 + deviceProfileName: light-color-level + - id: "5014/4246" + deviceLabel: OREiN Matter smart Bathroom Fan + vendorId: 0x1396 + productId: 0x1096 + deviceProfileName: fan-modular + - id: "5014/4247" + deviceLabel: OREiN Matter smart Bathroom Fan + vendorId: 0x1396 + productId: 0x1097 + deviceProfileName: fan-modular + - id: "5014/4215" + deviceLabel: Linkind Smart Light Bulb + vendorId: 0x1396 + productId: 0x1077 + deviceProfileName: light-color-level #Bosch Smart Home - id: "4617/12310" deviceLabel: Plug Compact [M] @@ -437,7 +482,277 @@ matterManufacturer: vendorId: 0x1339 productId: 0x0016 deviceProfileName: light-color-level-2000K-7000K - +#Govee + - id: "4999/24740" + deviceLabel: Govee Square Ceiling Light (12 inch) + vendorId: 0x1387 + productId: 0x60A4 + deviceProfileName: light-color-level + - id: "4999/28871" + deviceLabel: Govee Christmas String Lights 2 (Clear Wire) 164ft/50m + vendorId: 0x1387 + productId: 0x70C7 + deviceProfileName: light-color-level + - id: "4999/25043" + deviceLabel: Govee Neon Rope Light 2 9.8ft/3m + vendorId: 0x1387 + productId: 0x61D3 + deviceProfileName: light-color-level + - id: "4999/24595" + deviceLabel: Govee RGBWW Smart LED Bulb BR30 850lm + vendorId: 0x1387 + productId: 0x6013 + deviceProfileName: light-color-level + - id: "4999/25011" + deviceLabel: Govee Strip Light with Cover 9.8f/3m + vendorId: 0x1387 + productId: 0x61B3 + deviceProfileName: light-color-level + - id: "4999/24586" + deviceLabel: Govee RGBWW Smart LED Bulb A19 1200lm + vendorId: 0x1387 + productId: 0x600A + deviceProfileName: light-color-level + - id: "4999/26116" + deviceLabel: Govee HDMI 2.1 Sync Box 2 75-85 + vendorId: 0x1387 + productId: 0x6604 + deviceProfileName: light-color-level + - id: "4999/24729" + deviceLabel: Govee TV Backlight 3 Lite 75-85 + vendorId: 0x1387 + productId: 0x6099 + deviceProfileName: light-color-level + # - id: "4999/28871" + # deviceLabel: Govee Christmas String Lights 2 (Green Wire) 164ft/50m + # vendorId: 0x1387 + # productId: 0x70C7 + # deviceProfileName: light-color-level + - id: "4999/28873" + deviceLabel: Govee Christmas String Lights 2 (Green Wire) 328ft/100m + vendorId: 0x1387 + productId: 0x70C9 + deviceProfileName: light-color-level + - id: "4999/24754" + deviceLabel: Govee Tree Floor Lamp + vendorId: 0x1387 + productId: 0x60B2 + deviceProfileName: light-color-level + - id: "4999/25078" + deviceLabel: Govee Strip Light 2 Pro 32.8ft/10m + vendorId: 0x1387 + productId: 0x61F6 + deviceProfileName: light-color-level + - id: "4999/25077" + deviceLabel: Govee Strip Light 2 Pro 16.4ft/5m + vendorId: 0x1387 + productId: 0x61F5 + deviceProfileName: light-color-level + - id: "4999/24588" + deviceLabel: Govee RGBWW Smart LED Bulb E14 450lm + vendorId: 0x1387 + productId: 0x600C + deviceProfileName: light-color-level + - id: "4999/28868" + deviceLabel: Govee Christmas String Lights 2 (Clear Wire) 66ft/20m + vendorId: 0x1387 + productId: 0x70C4 + deviceProfileName: light-color-level + - id: "4999/25074" + deviceLabel: Govee Strip Light 2 Pro 6.56ft/2m + vendorId: 0x1387 + productId: 0x61F2 + deviceProfileName: light-color-level + - id: "4999/25013" + deviceLabel: Govee Strip Light with Cover 16.4ft/5m + vendorId: 0x1387 + productId: 0x61B5 + deviceProfileName: light-color-level + - id: "4999/24753" + deviceLabel: Govee Torchiere Floor Lamp + vendorId: 0x1387 + productId: 0x60B1 + deviceProfileName: light-color-level + - id: "4999/28869" + deviceLabel: Govee Christmas String Lights 2 (Green Wire) 99ft/30m + vendorId: 0x1387 + productId: 0x70C5 + deviceProfileName: light-color-level + - id: "4999/24592" + deviceLabel: Govee RGBWW Smart LED Bulb BR30 1200lm + vendorId: 0x1387 + productId: 0x6010 + deviceProfileName: light-color-level + # - id: "4999/28869" + # deviceLabel: Govee Christmas String Lights 2 (Clear Wire) 99ft/30m + # vendorId: 0x1387 + # productId: 0x70C5 + # deviceProfileName: light-color-level + - id: "4999/24607" + deviceLabel: Govee Recessed Downlight 2 (6 inch) + vendorId: 0x1387 + productId: 0x601F + deviceProfileName: light-color-level + - id: "4999/25045" + deviceLabel: Govee Neon Rope Light 2 16.4ft/5m + vendorId: 0x1387 + productId: 0x61D5 + deviceProfileName: light-color-level + - id: "4999/24603" + deviceLabel: Govee Recessed Downlight (4 inch) + vendorId: 0x1387 + productId: 0x601B + deviceProfileName: light-color-level + - id: "4999/24602" + deviceLabel: Govee Recessed Downlight (6 inch) + vendorId: 0x1387 + productId: 0x601A + deviceProfileName: light-color-level + - id: "4999/25014" + deviceLabel: Govee Strip Light with Cover 32.8ft/10m + vendorId: 0x1387 + productId: 0x61B6 + deviceProfileName: light-color-level + - id: "4999/24610" + deviceLabel: Govee Table Lamp 2 + vendorId: 0x1387 + productId: 0x6022 + deviceProfileName: light-color-level + - id: "4999/24606" + deviceLabel: Govee Recessed Downlight 2 (4 inch) + vendorId: 0x1387 + productId: 0x601E + deviceProfileName: light-color-level + - id: "4999/24589" + deviceLabel: Govee RGBWW Smart LED Bulb MR16 400lm + vendorId: 0x1387 + productId: 0x600D + deviceProfileName: light-color-level + - id: "4999/24700" + deviceLabel: Govee Floor Lamp 2 + vendorId: 0x1387 + productId: 0x607C + deviceProfileName: light-color-level + # - id: "4999/26116" + # deviceLabel: Govee HDMI 2.1 Sync Box 2 55-65 + # vendorId: 0x1387 + # productId: 0x6604 + # deviceProfileName: light-color-level + - id: "4999/24694" + deviceLabel: Govee Floor Lamp Basic + vendorId: 0x1387 + productId: 0x6076 + deviceProfileName: light-color-level + - id: "4999/24587" + deviceLabel: Govee RGBWW Smart LED Bulb E12 450lm + vendorId: 0x1387 + productId: 0x600B + deviceProfileName: light-color-level + - id: "4999/25017" + deviceLabel: Govee Strip Light with Skyline Kit 19.7ft/6m + vendorId: 0x1387 + productId: 0x61B9 + deviceProfileName: light-color-level + - id: "4999/24742" + deviceLabel: Govee Ceiling Light Pro (15 inch) + vendorId: 0x1387 + productId: 0x60A6 + deviceProfileName: light-color-level + - id: "4999/24666" + deviceLabel: Govee TV Backlight 3 Lite Kit 55-65 + vendorId: 0x1387 + productId: 0x605A + deviceProfileName: light-color-level + - id: "4999/24608" + deviceLabel: Govee Table Lamp 2 Pro Sound by JBL + vendorId: 0x1387 + productId: 0x6020 + deviceProfileName: light-color-level + # - id: "4999/28868" + # deviceLabel: Govee Christmas String Lights 2 (Green Wire) 66ft/20m + # vendorId: 0x1387 + # productId: 0x70C4 + # deviceProfileName: light-color-level + - id: "4999/25046" + deviceLabel: Govee Neon Rope Light 2 32.8ft/10m + vendorId: 0x1387 + productId: 0x61D6 + deviceProfileName: light-color-level + - id: "4999/25061" + deviceLabel: Govee COB Strip Light Pro 9.8ft/3m + vendorId: 0x1387 + productId: 0x61E5 + deviceProfileName: light-color-level + - id: "4999/24723" + deviceLabel: Govee Star Light Projector (Aurora) + vendorId: 0x1387 + productId: 0x6093 + deviceProfileName: light-color-level + - id: "4999/25062" + deviceLabel: Govee COB Strip Light Pro 16.4ft/5m + vendorId: 0x1387 + productId: 0x61E6 + deviceProfileName: light-color-level + - id: "4999/24752" + deviceLabel: Govee Uplighter Floor Lamp + vendorId: 0x1387 + productId: 0x60B0 + deviceProfileName: light-color-level + - id: "4999/24727" + deviceLabel: Govee TV Backlight 3 Lite 40-50 + vendorId: 0x1387 + productId: 0x6097 + deviceProfileName: light-color-level + - id: "4999/24582" + deviceLabel: Govee RGBWW Smart LED Bulb A19 1000lm + vendorId: 0x1387 + productId: 0x6006 + deviceProfileName: light-color-level + - id: "4999/25016" + deviceLabel: Govee Strip Light with Skyline Kit 13.1ft/4m + vendorId: 0x1387 + productId: 0x61B8 + deviceProfileName: light-color-level + - id: "4999/26737" + deviceLabel: Govee Christmas Sparkle String Lights 99ft + vendorId: 0x1387 + productId: 0x6871 + deviceProfileName: light-color-level + - id: "4999/26736" + deviceLabel: Govee Christmas Sparkle String Lights 66ft + vendorId: 0x1387 + productId: 0x6870 + deviceProfileName: light-color-level + - id: "4999/26272" + deviceLabel: Govee TV Backlight 3 Pro 55-65 + vendorId: 0x1387 + productId: 0x66A0 + deviceProfileName: light-color-level + # - id: "4999/26272" + # deviceLabel: Govee TV Backlight 3 Pro 75-85 + # vendorId: 0x1387 + # productId: 0x66A0 + # deviceProfileName: light-color-level + - id: "4999/28854" + deviceLabel: Govee Curtain Lights Pro + vendorId: 0x1387 + productId: 0x70B6 + deviceProfileName: light-color-level + - id: "4999/24733" + deviceLabel: Govee Galaxy Light Star Projector 2 Pro + vendorId: 0x1387 + productId: 0x609D + deviceProfileName: light-color-level + - id: "4999/24725" + deviceLabel: Govee Star Projector Light + vendorId: 0x1387 + productId: 0x6095 + deviceProfileName: light-color-level + - id: "4999/24724" + deviceLabel: Govee Star Projector Light + vendorId: 0x1387 + productId: 0x6094 + deviceProfileName: light-color-level # Hue - id: "4107/2049" deviceLabel: Hue W 1600 A21 E26 1P NAM @@ -542,6 +857,17 @@ matterManufacturer: vendorId: 0x1189 productId: 0x0633 deviceProfileName: plug-binary +#Ikea + - id: "4476/32768" + deviceLabel: BILRESA scroll wheel + vendorId: 0x117C + productId: 0x8000 + deviceProfileName: ikea-scroll + - id: "4476/32769" + deviceLabel: BILRESA dual button + vendorId: 0x117C + productId: 0x8001 + deviceProfileName: 2-button-battery #Innovation Matters - id: "4978/1" deviceLabel: M2D Bridge @@ -641,6 +967,11 @@ matterManufacturer: vendorId: 0x109B productId: 0x1006 deviceProfileName: light-level-motion + - id: "4251/4103" + deviceLabel: Decora Smart Wi-Fi (2nd Gen) Scene Controller Switch + vendorId: 0x109B + productId: 0x1007 + deviceProfileName: 3-button #LeTianPai - id: "5163/4097" deviceLabel: LeTianPai Smart Light Bulb @@ -794,6 +1125,156 @@ matterManufacturer: vendorId: 0x1423 productId: 0x00BF deviceProfileName: light-color-level + - id: "5155/163" + deviceLabel: LIFX Supercolor (A19) + vendorId: 0x1423 + productId: 0x00A3 + deviceProfileName: light-level-colorTemperature + - id: "5155/118" + deviceLabel: LIFX Lightstrip + vendorId: 0x1423 + productId: 0x0076 + deviceProfileName: light-level-colorTemperature + - id: "5155/221" + deviceLabel: LIFX Spot + vendorId: 0x1423 + productId: 0x00DD + deviceProfileName: light-level-colorTemperature + - id: "5155/144" + deviceLabel: LIFX String + vendorId: 0x1423 + productId: 0x0090 + deviceProfileName: light-level-colorTemperature + - id: "5155/216" + deviceLabel: LIFX Candle Color (B10) + vendorId: 0x1423 + productId: 0x00D8 + deviceProfileName: light-level-colorTemperature + - id: "5155/225" + deviceLabel: LIFX PAR38 + vendorId: 0x1423 + productId: 0x00E1 + deviceProfileName: light-level-colorTemperature + - id: "5155/186" + deviceLabel: LIFX Candle Color + vendorId: 0x1423 + productId: 0x00BA + deviceProfileName: light-level-colorTemperature + - id: "5155/202" + deviceLabel: LIFX Ceiling 13x26 + vendorId: 0x1423 + productId: 0x00CA + deviceProfileName: light-level-colorTemperature + - id: "5155/143" + deviceLabel: LIFX String + vendorId: 0x1423 + productId: 0x008F + deviceProfileName: light-level-colorTemperature + - id: "5155/166" + deviceLabel: LIFX Supercolour (BR30) + vendorId: 0x1423 + productId: 0x00A6 + deviceProfileName: light-level-colorTemperature + - id: "5155/167" + deviceLabel: LIFX Downlight + vendorId: 0x1423 + productId: 0x00A7 + deviceProfileName: light-level-colorTemperature + - id: "5155/207" + deviceLabel: LIFX Everyday Lightstrip + vendorId: 0x1423 + productId: 0x00CF + deviceProfileName: light-level-colorTemperature + - id: "5155/222" + deviceLabel: LIFX Path (Round) + vendorId: 0x1423 + productId: 0x00DE + deviceProfileName: light-level-colorTemperature + - id: "5155/203" + deviceLabel: LIFX String + vendorId: 0x1423 + productId: 0x00CB + deviceProfileName: light-level-colorTemperature + - id: "5155/218" + deviceLabel: LIFX Tube + vendorId: 0x1423 + productId: 0x00DA + deviceProfileName: light-level-colorTemperature + - id: "5155/214" + deviceLabel: LIFX Permanent Outdoor + vendorId: 0x1423 + productId: 0x00D6 + deviceProfileName: light-level-colorTemperature + - id: "5155/117" + deviceLabel: LIFX Lightstrip + vendorId: 0x1423 + productId: 0x0075 + deviceProfileName: light-level-colorTemperature + - id: "5155/223" + deviceLabel: LIFX Downlight (6 Retro Downlight) + vendorId: 0x1423 + productId: 0x00DF + deviceProfileName: light-level-colorTemperature + - id: "5155/224" + deviceLabel: LIFX Downlight (90mm Downlight) + vendorId: 0x1423 + productId: 0x00E0 + deviceProfileName: light-level-colorTemperature + - id: "5155/204" + deviceLabel: LIFX String + vendorId: 0x1423 + productId: 0x00CC + deviceProfileName: light-level-colorTemperature + - id: "5155/206" + deviceLabel: LIFX Neon + vendorId: 0x1423 + productId: 0x00CE + deviceProfileName: light-level-colorTemperature + - id: "5155/164" + deviceLabel: LIFX Supercolor (BR30) + vendorId: 0x1423 + productId: 0x00A4 + deviceProfileName: light-level-colorTemperature + - id: "5155/120" + deviceLabel: LIFX Beam + vendorId: 0x1423 + productId: 0x0078 + deviceProfileName: light-level-colorTemperature + - id: "5155/208" + deviceLabel: LIFX Everyday Lightstrip + vendorId: 0x1423 + productId: 0x00D0 + deviceProfileName: light-level-colorTemperature + - id: "5155/165" + deviceLabel: LIFX Supercolour (A19) + vendorId: 0x1423 + productId: 0x00A5 + deviceProfileName: light-level-colorTemperature + - id: "5155/142" + deviceLabel: LIFX Neon + vendorId: 0x1423 + productId: 0x008E + deviceProfileName: light-level-colorTemperature + - id: "5155/141" + deviceLabel: LIFX Neon + vendorId: 0x1423 + productId: 0x008D + deviceProfileName: light-level-colorTemperature + - id: "5155/177" + deviceLabel: LIFX Ceiling + vendorId: 0x1423 + productId: 0x00B1 + deviceProfileName: light-level-colorTemperature + - id: "5155/170" + deviceLabel: LIFX Supercolour (A21) + vendorId: 0x1423 + productId: 0x00AA + deviceProfileName: light-level-colorTemperature + - id: "5155/205" + deviceLabel: LIFX Neon + vendorId: 0x1423 + productId: 0x00CD + deviceProfileName: light-level-colorTemperature #LG - id: "4142/8784" deviceLabel: LG Smart Button (1 Button) @@ -873,6 +1354,12 @@ matterManufacturer: vendorId: 0x137F productId: 0x027B deviceProfileName: plug-binary +#Onvis + - id: "5181/4097" + deviceLabel: Onvis Smart Plug S4EU + vendorId: 0x143D + productId: 0x1001 + deviceProfileName: plug-binary #Osram - id: "4489/2564" deviceLabel: OSRAM MATTER PLUG UK @@ -884,6 +1371,466 @@ matterManufacturer: vendorId: 0x1189 productId: 0x0AC5 deviceProfileName: light-color-level + - id: "4489/61442" + deviceLabel: OSRAM MATTER CLASSIC A 100W + vendorId: 0x1189 + productId: 0xF002 + deviceProfileName: light-color-level + - id: "4489/2755" + deviceLabel: SMART MAT A60 RGBW 827 FR E27 + vendorId: 0x1189 + productId: 0x0AC3 + deviceProfileName: light-color-level + - id: "4489/61444" + deviceLabel: OSRAM MATTER PLUG UK + vendorId: 0x1189 + productId: 0xF004 + deviceProfileName: plug-binary + - id: "4489/61443" + deviceLabel: OSRAM MATTER PLUG EU WH + vendorId: 0x1189 + productId: 0xF003 + deviceProfileName: plug-binary + - id: "4489/61441" + deviceLabel: OSRAM MATTER CLASSIC A 60W + vendorId: 0x1189 + productId: 0xF001 + deviceProfileName: light-color-level + - id: "4489/2353" + deviceLabel: SMART MATTER FLOORCORN200 MGC WT + vendorId: 0x1189 + productId: 0x0931 + deviceProfileName: light-color-level + - id: "4489/2350" + deviceLabel: SMART MATTER FLOORCORN140 MGC BK + vendorId: 0x1189 + productId: 0x092E + deviceProfileName: light-color-level + - id: "4489/2352" + deviceLabel: SMART MATTER FLOORCORN140 MGC WT + vendorId: 0x1189 + productId: 0x0930 + deviceProfileName: light-color-level + - id: "4489/2351" + deviceLabel: SMART MATTER FLOORCORN200 MGC BK + vendorId: 0x1189 + productId: 0x092F + deviceProfileName: light-color-level + - id: "4489/2715" + deviceLabel: SMART MAT B40 TW 827 FR E14 + vendorId: 0x1189 + productId: 0x0A9B + deviceProfileName: light-level-colorTemperature + - id: "4489/2686" + deviceLabel: SMART MAT A53 DIM FILGD 824 E27 + vendorId: 0x1189 + productId: 0x0A7E + deviceProfileName: light-level + - id: "4489/2725" + deviceLabel: SMART MAT PAR16 RGBW GU10 + vendorId: 0x1189 + productId: 0x0AA5 + deviceProfileName: light-color-level + - id: "4489/2766" + deviceLabel: SMART MAT B40 RGBW 827 FR E14 + vendorId: 0x1189 + productId: 0x0ACE + deviceProfileName: light-color-level + - id: "4489/2765" + deviceLabel: SMART MAT P40 RGBW 827 FR E14 + vendorId: 0x1189 + productId: 0x0ACD + deviceProfileName: light-color-level + - id: "4489/2764" + deviceLabel: SMART MAT G95 RGBW 827 FR E27 + vendorId: 0x1189 + productId: 0x0ACC + deviceProfileName: light-color-level + - id: "4489/2774" + deviceLabel: SMART MAT A40 FIL RGBW 827 E27 + vendorId: 0x1189 + productId: 0x0AD6 + deviceProfileName: light-color-level + - id: "4489/2775" + deviceLabel: SMART MAT E40 FIL RGBW 827 E27 + vendorId: 0x1189 + productId: 0x0AD7 + deviceProfileName: light-color-level + - id: "4489/2776" + deviceLabel: SMART MAT G40 FIL RGBW 827 E27 + vendorId: 0x1189 + productId: 0x0AD8 + deviceProfileName: light-color-level + - id: "4489/2837" + deviceLabel: SMART MAT A75 RGBW 827 FR E27 + vendorId: 0x1189 + productId: 0x0B15 + deviceProfileName: light-color-level + - id: "4489/2713" + deviceLabel: SMART MAT A60 TW 827 FR E27 + vendorId: 0x1189 + productId: 0x0A99 + deviceProfileName: light-level-colorTemperature + - id: "4489/2711" + deviceLabel: SMART MAT A100 TW 827 FR E27 + vendorId: 0x1189 + productId: 0x0A97 + deviceProfileName: light-level-colorTemperature + - id: "4489/2716" + deviceLabel: SMART MAT P40 TW 827 FR E14 + vendorId: 0x1189 + productId: 0x0A9C + deviceProfileName: light-level-colorTemperature + - id: "4489/2726" + deviceLabel: SMART MAT PAR16 TW 827 GU10 + vendorId: 0x1189 + productId: 0x0AA6 + deviceProfileName: light-level-colorTemperature + - id: "4489/2678" + deviceLabel: SMART MAT E60 DIM FIL 827 E27 + vendorId: 0x1189 + productId: 0x0A76 + deviceProfileName: light-level + - id: "4489/2676" + deviceLabel: SMART MAT G60 DIM FIL 827 E27 + vendorId: 0x1189 + productId: 0x0A74 + deviceProfileName: light-level + - id: "4489/2687" + deviceLabel: SMART MAT B40 DIM FIL 827 E14 + vendorId: 0x1189 + productId: 0x0A7F + deviceProfileName: light-level + - id: "4489/2685" + deviceLabel: SMART MAT E53 DIM FILGD 824 E27 + vendorId: 0x1189 + productId: 0x0A7D + deviceProfileName: light-level + - id: "4489/2684" + deviceLabel: SMART MAT G53 DIM FILGD 824 E27 + vendorId: 0x1189 + productId: 0x0A7C + deviceProfileName: light-level + - id: "4489/2681" + deviceLabel: SMART MAT A60 DIM FIL 827 E27 + vendorId: 0x1189 + productId: 0x0A79 + deviceProfileName: light-level + - id: "4489/2682" + deviceLabel: SMART MAT P40 DIM FIL 827 E14 + vendorId: 0x1189 + productId: 0x0A7A + deviceProfileName: light-level + - id: "4489/2727" + deviceLabel: SMART MAT PAR16 DIM GU10 + vendorId: 0x1189 + productId: 0x0AA7 + deviceProfileName: light-level + - id: "4489/2712" + deviceLabel: SMART MAT G95 100 TW 827 E27 + vendorId: 0x1189 + productId: 0x0A98 + deviceProfileName: light-level-colorTemperature + - id: "4489/2836" + deviceLabel: SMART MAT A75 TW 827 FR E27 + vendorId: 0x1189 + productId: 0x0B14 + deviceProfileName: light-level-colorTemperature + - id: "4489/2514" + deviceLabel: SMART MAT ORBIS DL 200 TW WT + vendorId: 0x1189 + productId: 0x09D2 + deviceProfileName: light-color-level + - id: "4489/2518" + deviceLabel: SMART MAT ORBIS DL 400 TW WT + vendorId: 0x1189 + productId: 0x09D6 + deviceProfileName: light-color-level + - id: "4489/2519" + deviceLabel: SMART MAT ORBIS DL SQ400 TW WT + vendorId: 0x1189 + productId: 0x09D7 + deviceProfileName: light-color-level + - id: "4489/2763" + deviceLabel: SMART MAT A60 RGBW 827 FR B22D + vendorId: 0x1189 + productId: 0x0ACB + deviceProfileName: light-color-level + - id: "4489/2756" + deviceLabel: SMART MAT A100 RGBW 827 FR E27 + vendorId: 0x1189 + productId: 0x0AC4 + deviceProfileName: light-color-level + - id: "4489/3027" + deviceLabel: SMART MAT DECORWALLSWAN300X150TW + vendorId: 0x1189 + productId: 0x0BD3 + deviceProfileName: light-level-colorTemperature + - id: "4489/2994" + deviceLabel: SMART MAT PLANON FL 120X30 TW + vendorId: 0x1189 + productId: 0x0BB2 + deviceProfileName: light-level-colorTemperature + - id: "4489/3032" + deviceLabel: SMART WIFI MAT BATH W400 IP44 TW + vendorId: 0x1189 + productId: 0x0BD8 + deviceProfileName: light-level-colorTemperature + - id: "4489/3026" + deviceLabel: SMART WIFI MAT WAVE300X300IP44TW + vendorId: 0x1189 + productId: 0x0BD2 + deviceProfileName: light-level-colorTemperature + - id: "4489/2957" + deviceLabel: SMART WIFI MAT TRACKSP OSAKA TW + vendorId: 0x1189 + productId: 0x0B8D + deviceProfileName: light-level-colorTemperature + - id: "4489/3025" + deviceLabel: SMART WIFI MAT CYLINDER 450 TW + vendorId: 0x1189 + productId: 0x0BD1 + deviceProfileName: light-level-colorTemperature + - id: "4489/2969" + deviceLabel: SMART MAT PLANON PLUS 120X30TW + vendorId: 0x1189 + productId: 0x0B99 + deviceProfileName: light-level-colorTemperature + - id: "4489/3028" + deviceLabel: SMART MAT DISC 400 IP44 TW + vendorId: 0x1189 + productId: 0x0BD4 + deviceProfileName: light-level-colorTemperature + - id: "4489/2971" + deviceLabel: SMART MAT PLANON FL 80X10TW + vendorId: 0x1189 + productId: 0x0B9B + deviceProfileName: light-level-colorTemperature + - id: "4489/3014" + deviceLabel: SMART MAT PLANONFLSPARK45X45TW + vendorId: 0x1189 + productId: 0x0BC6 + deviceProfileName: light-level-colorTemperature + - id: "4489/3023" + deviceLabel: SMART WIFI MAT MAGNET 600X300 TW + vendorId: 0x1189 + productId: 0x0BCF + deviceProfileName: light-level-colorTemperature + - id: "4489/3021" + deviceLabel: SMART WIFI MAT AQUA280X160IP44TW + vendorId: 0x1189 + productId: 0x0BCD + deviceProfileName: light-level-colorTemperature + - id: "4489/3035" + deviceLabel: SMART WIFI MAT DUPLO 300 IP44 TW + vendorId: 0x1189 + productId: 0x0BDB + deviceProfileName: light-level-colorTemperature + - id: "4489/3024" + deviceLabel: SMART WIFI MAT DUPLO 580 IP44 TW + vendorId: 0x1189 + productId: 0x0BD0 + deviceProfileName: light-level-colorTemperature + - id: "4489/2998" + deviceLabel: SMART WIFI MAT DL SLIM 120 TW + vendorId: 0x1189 + productId: 0x0BB6 + deviceProfileName: light-level-colorTemperature + - id: "4489/3020" + deviceLabel: SMART MATDECORWALLTWIST230X127TW + vendorId: 0x1189 + productId: 0x0BCC + deviceProfileName: light-level-colorTemperature + - id: "4489/3049" + deviceLabel: SMART WIFI MAT TRACKSP CIRCLE TW + vendorId: 0x1189 + productId: 0x0BE9 + deviceProfileName: light-level-colorTemperature + - id: "4489/3029" + deviceLabel: SMART WIFI MAT DUPLO 450 IP44 TW + vendorId: 0x1189 + productId: 0x0BD5 + deviceProfileName: light-level-colorTemperature + - id: "4489/2991" + deviceLabel: SMART MAT PLANON PLUS 60X60 TW + vendorId: 0x1189 + productId: 0x0BAF + deviceProfileName: light-level-colorTemperature + - id: "4489/2985" + deviceLabel: SMART MAT PLANON PLUS 30X30 TW + vendorId: 0x1189 + productId: 0x0BA9 + deviceProfileName: light-level-colorTemperature + - id: "4489/3033" + deviceLabel: SMART WIFI MAT BATH W300 IP44 TW + vendorId: 0x1189 + productId: 0x0BD9 + deviceProfileName: light-level-colorTemperature + - id: "4489/2999" + deviceLabel: SMART WIFI MAT DL SLIM 225 TW + vendorId: 0x1189 + productId: 0x0BB7 + deviceProfileName: light-level-colorTemperature + - id: "4489/3037" + deviceLabel: SMART WIFI MAT MAGNET 300X300 TW + vendorId: 0x1189 + productId: 0x0BDD + deviceProfileName: light-level-colorTemperature + - id: "4489/2990" + deviceLabel: SMART MAT PLANON PLUS 60X30 TW + vendorId: 0x1189 + productId: 0x0BAE + deviceProfileName: light-level-colorTemperature + - id: "4489/3047" + deviceLabel: SMART WIFI MAT TRACKSP OSAKA TW + vendorId: 0x1189 + productId: 0x0BE7 + deviceProfileName: light-level-colorTemperature + - id: "4489/3031" + deviceLabel: SMART WIFI MAT MAGNET 600X300 TW + vendorId: 0x1189 + productId: 0x0BD7 + deviceProfileName: light-level-colorTemperature + - id: "4489/3034" + deviceLabel: SMART WIFI MAT DISC 300 IP44 TW + vendorId: 0x1189 + productId: 0x0BDA + deviceProfileName: light-level-colorTemperature + - id: "4489/3030" + deviceLabel: SMART WIFI MAT MAGNET 450X450 TW + vendorId: 0x1189 + productId: 0x0BD6 + deviceProfileName: light-level-colorTemperature + - id: "4489/3022" + deviceLabel: SMART WIFI MAT AQUA200X200IP44TW + vendorId: 0x1189 + productId: 0x0BCE + deviceProfileName: light-level-colorTemperature + - id: "4489/2993" + deviceLabel: SMART MAT PLANON FL 30X30 TW + vendorId: 0x1189 + productId: 0x0BB1 + deviceProfileName: light-level-colorTemperature + - id: "4489/3048" + deviceLabel: SMART WIFI MAT TRACKSP CIRCLE TW + vendorId: 0x1189 + productId: 0x0BE8 + deviceProfileName: light-level-colorTemperature + - id: "4489/2997" + deviceLabel: SMART WIFI MAT DL SLIM 85 TW WT + vendorId: 0x1189 + productId: 0x0BB5 + deviceProfileName: light-level-colorTemperature + - id: "4489/2949" + deviceLabel: SMART WIFI MAT SPIRAL 500 TW + vendorId: 0x1189 + productId: 0x0B85 + deviceProfileName: light-level-colorTemperature + - id: "4489/3036" + deviceLabel: SMART WFMTDECORWALLSWAN200X200TW + vendorId: 0x1189 + productId: 0x0BDC + deviceProfileName: light-level-colorTemperature + - id: "4489/2995" + deviceLabel: SMART MAT PLANON FL 120X10 TW + vendorId: 0x1189 + productId: 0x0BB3 + deviceProfileName: light-level-colorTemperature + - id: "4489/2987" + deviceLabel: SMART MAT PLANON PLUS 45X45 TW + vendorId: 0x1189 + productId: 0x0BAB + deviceProfileName: light-level-colorTemperature + - id: "4489/3018" + deviceLabel: SMART WFMTDECORWALLWOOD210X110TW + vendorId: 0x1189 + productId: 0x0BCA + deviceProfileName: light-level-colorTemperature + - id: "4489/3019" + deviceLabel: SMART MATDECORWALLTWIST230X127TW + vendorId: 0x1189 + productId: 0x0BCB + deviceProfileName: light-level-colorTemperature + - id: "4489/3055" + deviceLabel: SMART WIFI MAT FLOOD 50W RGBW + vendorId: 0x1189 + productId: 0x0BEF + deviceProfileName: light-color-level + - id: "4489/2970" + deviceLabel: SMART MAT PLANON PLUS60X60RGBTW + vendorId: 0x1189 + productId: 0x0B9A + deviceProfileName: light-color-level + - id: "4489/3057" + deviceLabel: SMART WIFI MAT FLOOD 20W RGBW + vendorId: 0x1189 + productId: 0x0BF1 + deviceProfileName: light-color-level + - id: "4489/2973" + deviceLabel: SMART MAT PLANON PLUS45X45RGBW + vendorId: 0x1189 + productId: 0x0B9D + deviceProfileName: light-color-level + - id: "4489/2974" + deviceLabel: SMART MAT PLANON PLUS60X30RGBW + vendorId: 0x1189 + productId: 0x0B9E + deviceProfileName: light-color-level + - id: "4489/3059" + deviceLabel: SMART WIFI MAT CLARIA 490RGBTW + vendorId: 0x1189 + productId: 0x0BF3 + deviceProfileName: light-color-level + - id: "4489/3058" + deviceLabel: SMART WIFI MAT FLOOD 30W RGBW + vendorId: 0x1189 + productId: 0x0BF2 + deviceProfileName: light-color-level + - id: "4489/2972" + deviceLabel: SMART MAT PLANON PLUS60X60RGBW + vendorId: 0x1189 + productId: 0x0B9C + deviceProfileName: light-color-level + - id: "4489/2984" + deviceLabel: SMART MAT PLANON PLUS120X30RGBW + vendorId: 0x1189 + productId: 0x0BA8 + deviceProfileName: light-color-level + - id: "4489/3001" + deviceLabel: SMART WIFI MAT SP170 110DEGRGBTW + vendorId: 0x1189 + productId: 0x0BB9 + deviceProfileName: light-color-level + - id: "4489/2992" + deviceLabel: SMART MAT PLANON PLUS30X30RGBW + vendorId: 0x1189 + productId: 0x0BB0 + deviceProfileName: light-color-level + - id: "4489/3056" + deviceLabel: SMART WIFI MAT FLOOD 10W RGBW + vendorId: 0x1189 + productId: 0x0BF0 + deviceProfileName: light-color-level + - id: "4489/3016" + deviceLabel: SMART OUTD MAT BEAMADJWALL RGBTW + vendorId: 0x1189 + productId: 0x0BC8 + deviceProfileName: light-color-level + - id: "4489/3000" + deviceLabel: SMART WIFI MAT SP 86 100DEGRGBTW + vendorId: 0x1189 + productId: 0x0BB8 + deviceProfileName: light-color-level + - id: "4489/3003" + deviceLabel: SMART WIFI MAT STEA485RD RGBTW + vendorId: 0x1189 + productId: 0x0BBB + deviceProfileName: light-color-level + - id: "4489/2963" + deviceLabel: SMART WIFI MAT FLOOD 100W RGBW + vendorId: 0x1189 + productId: 0x0B93 + deviceProfileName: light-color-level #Shelly - id: "5264/1" deviceLabel: Shelly Plug S MTR Gen3 @@ -2799,6 +3746,26 @@ matterManufacturer: vendorId: 0x100b productId: 0x228F deviceProfileName: light-color-level-2200K-6500K + - id: "4107/8954" + deviceLabel: WiZ Gradient Light Bars + vendorId: 0x100B + productId: 0x22FA + deviceProfileName: light-color-level + - id: "4107/8953" + deviceLabel: WiZ Gradient Light Bars + vendorId: 0x100B + productId: 0x22F9 + deviceProfileName: light-color-level + - id: "4107/8949" + deviceLabel: WiZ Gradient Floor lamp + vendorId: 0x100B + productId: 0x22F5 + deviceProfileName: light-color-level + - id: "4107/8948" + deviceLabel: WiZ Gradient Floor lamp + vendorId: 0x100B + productId: 0x22F4 + deviceProfileName: light-color-level #Zemismart - id: "5020/61154" deviceLabel: Zemismart Inline Module @@ -3040,13 +4007,66 @@ matterGeneric: deviceTypes: - id: 0x010D # Extended Color Light - id: 0x002B # Fan - deviceProfileName: light-color-level-fan + deviceProfileName: fan-modular - id: "matter/dimmable/light/motion" deviceLabel: Matter Dimmable Light Occupancy Sensor deviceTypes: - id: 0x0101 # Dimmable Light - id: 0x0107 # Occupancy Sensor deviceProfileName: light-level-motion + - id: "matter/camera" + deviceLabel: Matter Camera + deviceTypes: + - id: 0x0142 # Camera + deviceProfileName: camera + - id: "matter/on-off/fan/light" + deviceLabel: Matter OnOff Fan Light + deviceTypes: + - id: 0x002B # Fan + - id: 0x0100 # OnOff Light + deviceProfileName: fan-modular + - id: "matter/dimmable/fan/light" + deviceLabel: Matter Dimmable Fan Light + deviceTypes: + - id: 0x002B # Fan + - id: 0x0101 # Dimmable Light + deviceProfileName: fan-modular + - id: "matter/colorTemperature/fan/light" + deviceLabel: Matter Color Temperature Fan Light + deviceTypes: + - id: 0x002B # Fan + - id: 0x010C # Color Temperature Light + deviceProfileName: fan-modular + - id: "matter/color/fan/light" + deviceLabel: Matter Color Fan Light + deviceTypes: + - id: 0x002B # Fan + - id: 0x010D # Extended Color Light + deviceProfileName: fan-modular + - id: "matter/on-off/fan/plug" + deviceLabel: Matter OnOff Fan Plug + deviceTypes: + - id: 0x002B # Fan + - id: 0x010A # On Off Plug-in Unit + deviceProfileName: fan-modular + - id: "matter/dimmable/fan/plug" + deviceLabel: Matter Dimmable Fan Plug + deviceTypes: + - id: 0x002B # Fan + - id: 0x010B # Dimmable Plug-in Unit + deviceProfileName: fan-modular + - id: "matter/mounted/on-off/control/fan" + deviceLabel: Matter Mounted OnOff Control Fan + deviceTypes: + - id: 0x002B # Fan + - id: 0x010F # Mounted On/Off Control + deviceProfileName: fan-modular + - id: "matter/mounted/dim/load/control/fan" + deviceLabel: Matter Mounted Dimmable Load Control Fan + deviceTypes: + - id: 0x002B # Fan + - id: 0x0110 # Mounted Dimmable Load Control + deviceProfileName: fan-modular matterThing: - id: SmartThings/MatterThing diff --git a/drivers/SmartThings/matter-switch/profiles/3-button-motion.yml b/drivers/SmartThings/matter-switch/profiles/3-button-motion.yml new file mode 100644 index 0000000000..2852dd2ccd --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/3-button-motion.yml @@ -0,0 +1,26 @@ +name: 3-button-motion +components: + - id: main + capabilities: + - id: button + version: 1 + - id: motionSensor + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController + - id: button2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button3 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/profiles/6-button-motion.yml b/drivers/SmartThings/matter-switch/profiles/6-button-motion.yml new file mode 100644 index 0000000000..f191e95fdc --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/6-button-motion.yml @@ -0,0 +1,44 @@ +name: 6-button-motion +components: + - id: main + capabilities: + - id: button + version: 1 + - id: motionSensor + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController + - id: button2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button3 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button4 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button5 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button6 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/profiles/camera.yml b/drivers/SmartThings/matter-switch/profiles/camera.yml new file mode 100644 index 0000000000..7f62319984 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/camera.yml @@ -0,0 +1,156 @@ +name: camera +components: + - id: main + capabilities: + - id: webrtc + version: 1 + optional: true + - id: videoCapture2 + version: 1 + optional: true + - id: videoStreamSettings + version: 1 + optional: true + - id: imageCapture + version: 1 + optional: true + - id: mechanicalPanTiltZoom + version: 1 + optional: true + - id: hdr + version: 1 + optional: true + - id: nightVision + version: 1 + optional: true + - id: imageControl + version: 1 + optional: true + - id: audioRecording + version: 1 + optional: true + - id: sounds + version: 1 + optional: true + - id: cameraPrivacyMode + version: 1 + optional: true + - id: zoneManagement + version: 1 + optional: true + - id: localMediaStorage + version: 1 + optional: true + - id: cameraViewportSettings + version: 1 + optional: true + - id: motionSensor + version: 1 + optional: true + - id: vision.clipAnalysis + version: 1 + optional: true + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Camera + - id: statusLed + optional: true + capabilities: + - id: switch + version: 1 + optional: true + - id: mode + version: 1 + optional: true + - id: speaker + optional: true + capabilities: + - id: audioMute + version: 1 + optional: true + - id: audioVolume + version: 1 + optional: true + - id: microphone + optional: true + capabilities: + - id: audioMute + version: 1 + optional: true + - id: audioVolume + version: 1 + optional: true + - id: doorbell + optional: true + capabilities: + - id: button + version: 1 + optional: true +deviceConfig: + dashboard: + states: + - component: main + capability: imageCapture + version: 1 + values: + - label: "{{___PO_CODE_SAMSUNGELECTRONICS.IM_DEFAULT_IMAGE_CAPTURE}}" + visibleCondition: + component: main + capability: imageCapture + version: 1 + value: captureTime.value + valueType: string + operator: CONTAINS + operand: T + isOffline: false + basicPlus: + - displayType: camera + camera: + image: + component: main + capability: imageCapture + version: 1 + value: image.value + overlayIcons: + - iconUrl: "res://ic_camera_motion_detected" + visibleCondition: + component: main + capability: motionSensor + version: 1 + value: motion.value + valueType: string + operator: EQUALS + operand: "active" + detailView: + - component: main + capability: webrtc + version: 1 + - component: main + capability: mechanicalPanTiltZoom + version: 1 + - component: main + capability: motionSensor + version: 1 + automation: + conditions: + - component: main + capability: motionSensor + version: 1 + actions: + - component: main + capability: videoCapture2 + version: 1 + - component: main + capability: imageCapture + version: 1 + dpInfo: + - os: ios + dpUri: "storyboard://HMVSController/HMVSViewController" + - os: android + dpUri: "plugin://com.samsung.android.plugin.camera" +metadata: + mnmn: SmartThingsEdge + vid: matter-camera diff --git a/drivers/SmartThings/matter-switch/profiles/fan-modular.yml b/drivers/SmartThings/matter-switch/profiles/fan-modular.yml new file mode 100644 index 0000000000..878c9ed615 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/fan-modular.yml @@ -0,0 +1,17 @@ +name: fan-modular +components: +- id: main + capabilities: + - id: fanMode + version: 1 + optional: true + - id: fanSpeedPercent + version: 1 + optional: true + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Fan + diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml new file mode 100644 index 0000000000..4dc77d026d --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml @@ -0,0 +1,29 @@ +name: ikea-scroll +components: + - id: main + label: Group 1 + capabilities: + - id: button + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController + - id: group2 + label: Group 2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: group3 + label: Group 3 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController diff --git a/drivers/SmartThings/matter-switch/profiles/light-energy-powerConsumption.yml b/drivers/SmartThings/matter-switch/profiles/light-energy-powerConsumption.yml new file mode 100644 index 0000000000..1f62938b9d --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/light-energy-powerConsumption.yml @@ -0,0 +1,16 @@ +name: light-energy-powerConsumption +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-energy-powerConsumption.yml b/drivers/SmartThings/matter-switch/profiles/light-level-energy-powerConsumption.yml new file mode 100644 index 0000000000..03963ccbd2 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/light-level-energy-powerConsumption.yml @@ -0,0 +1,22 @@ +name: light-level-energy-powerConsumption +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + config: + values: + - key: "level.value" + range: [1, 100] + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-power.yml b/drivers/SmartThings/matter-switch/profiles/light-level-power.yml new file mode 100644 index 0000000000..23625ada16 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/light-level-power.yml @@ -0,0 +1,20 @@ +name: light-level-power +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + config: + values: + - key: "level.value" + range: [1, 100] + - id: powerMeter + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light diff --git a/drivers/SmartThings/matter-switch/profiles/light-power.yml b/drivers/SmartThings/matter-switch/profiles/light-power.yml new file mode 100644 index 0000000000..27e07d0a49 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/light-power.yml @@ -0,0 +1,14 @@ +name: light-power +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: powerMeter + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/init.lua new file mode 100644 index 0000000000..fb7a38e63b --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/init.lua @@ -0,0 +1,53 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local DescriptorServerAttributes = require "embedded_clusters.Descriptor.server.attributes" + +local Descriptor = {} + +Descriptor.ID = 0x001D +Descriptor.NAME = "Descriptor" +Descriptor.server = {} +Descriptor.client = {} +Descriptor.server.attributes = DescriptorServerAttributes:set_parent_cluster(Descriptor) + +function Descriptor:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0003] = "PartsList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function Descriptor:get_server_command_by_id(command_id) + local server_id_map = { + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + +Descriptor.attribute_direction_map = { + ["PartsList"] = "server", +} + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = Descriptor.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, Descriptor.NAME)) + end + return Descriptor[direction].attributes[key] +end +Descriptor.attributes = {} +setmetatable(Descriptor.attributes, attribute_helper_mt) + +setmetatable(Descriptor, {__index = cluster_base}) + +return Descriptor + diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/server/attributes/PartsList.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/server/attributes/PartsList.lua new file mode 100644 index 0000000000..7c5b24d28d --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/server/attributes/PartsList.lua @@ -0,0 +1,78 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local PartsList = { + ID = 0x0003, + NAME = "PartsList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint16", +} + +function PartsList:augment_type(data_type_obj) + for i, v in ipairs(data_type_obj.elements) do + data_type_obj.elements[i] = data_types.validate_or_build_type(v, PartsList.element_type) + end +end + +function PartsList:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function PartsList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function PartsList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function PartsList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function PartsList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function PartsList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(PartsList, {__call = PartsList.new_value, __index = PartsList.base_type}) +return PartsList + diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/server/attributes/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/server/attributes/init.lua new file mode 100644 index 0000000000..8a3d9ea3c2 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/Descriptor/server/attributes/init.lua @@ -0,0 +1,27 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("embedded_clusters.Descriptor.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local DescriptorServerAttributes = {} + +function DescriptorServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(DescriptorServerAttributes, attr_mt) + +return DescriptorServerAttributes + diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/init.lua index 8c96de0563..0f564673a9 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/init.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/init.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local ElectricalEnergyMeasurementServerAttributes = require "embedded_clusters.ElectricalEnergyMeasurement.server.attributes" local ElectricalEnergyMeasurementTypes = require "embedded_clusters.ElectricalEnergyMeasurement.types" diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua index 4e35c264f0..2d41790440 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua index 607faa2161..5daccf48ab 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/init.lua index e137f08918..57bc0d1f72 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/init.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua index 950b260227..a4c58a3646 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local StructureABC = require "st.matter.data_types.base_defs.StructureABC" local EnergyMeasurementStruct = {} diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/Feature.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/Feature.lua index 7b9bc03714..e3db76f49d 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/Feature.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/Feature.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" local Feature = {} diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/init.lua index 5241c3b864..ec29b53e05 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/init.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalPowerMeasurement/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalPowerMeasurement/init.lua index 222c80090b..c9061e3cc4 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalPowerMeasurement/init.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalPowerMeasurement/init.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local ElectricalPowerMeasurementServerAttributes = require "embedded_clusters.ElectricalPowerMeasurement.server.attributes" diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalPowerMeasurement/server/attributes/ActivePower.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalPowerMeasurement/server/attributes/ActivePower.lua index 457f6484af..f1696509f5 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalPowerMeasurement/server/attributes/ActivePower.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalPowerMeasurement/server/attributes/ActivePower.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalPowerMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalPowerMeasurement/server/attributes/init.lua index b1a8d57bde..6de69b94ff 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalPowerMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalPowerMeasurement/server/attributes/init.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/init.lua new file mode 100644 index 0000000000..0205e69b1f --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/init.lua @@ -0,0 +1,55 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local PowerTopologyServerAttributes = require "embedded_clusters.PowerTopology.server.attributes" +local PowerTopologyTypes = require "embedded_clusters.PowerTopology.types" + +local PowerTopology = {} + +PowerTopology.ID = 0x009C +PowerTopology.NAME = "PowerTopology" +PowerTopology.server = {} +PowerTopology.client = {} +PowerTopology.server.attributes = PowerTopologyServerAttributes:set_parent_cluster(PowerTopology) +PowerTopology.types = PowerTopologyTypes + +function PowerTopology:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "AvailableEndpoints", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +PowerTopology.attribute_direction_map = { + ["AvailableEndpoints"] = "server", +} + +PowerTopology.FeatureMap = PowerTopology.types.Feature + +function PowerTopology.are_features_supported(feature, feature_map) + if (PowerTopology.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = PowerTopology.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, PowerTopology.NAME)) + end + return PowerTopology[direction].attributes[key] +end +PowerTopology.attributes = {} +setmetatable(PowerTopology.attributes, attribute_helper_mt) + +setmetatable(PowerTopology, {__index = cluster_base}) + +return PowerTopology + diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/server/attributes/AvailableEndpoints.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/server/attributes/AvailableEndpoints.lua new file mode 100644 index 0000000000..1536301d11 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/server/attributes/AvailableEndpoints.lua @@ -0,0 +1,72 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local AvailableEndpoints = { + ID = 0x0000, + NAME = "AvailableEndpoints", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint16", +} + +function AvailableEndpoints:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function AvailableEndpoints:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function AvailableEndpoints:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function AvailableEndpoints:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function AvailableEndpoints:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AvailableEndpoints:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(AvailableEndpoints, {__call = AvailableEndpoints.new_value, __index = AvailableEndpoints.base_type}) +return AvailableEndpoints + diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/server/attributes/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/server/attributes/init.lua new file mode 100644 index 0000000000..ff88697a0e --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/server/attributes/init.lua @@ -0,0 +1,23 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local attr_mt = {} +attr_mt.__index = function(self, key) + local req_loc = string.format("embedded_clusters.PowerTopology.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + return raw_def +end + +local PowerTopologyServerAttributes = {} + +function PowerTopologyServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(PowerTopologyServerAttributes, attr_mt) + +return PowerTopologyServerAttributes + diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/types/Feature.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/types/Feature.lua new file mode 100644 index 0000000000..c2fe8087de --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/types/Feature.lua @@ -0,0 +1,32 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.NODE_TOPOLOGY = 0x0001 +Feature.TREE_TOPOLOGY = 0x0002 +Feature.SET_TOPOLOGY = 0x0004 +Feature.DYNAMIC_POWER_FLOW = 0x0008 + +function Feature.bits_are_valid(feature) + local max = + Feature.NODE_TOPOLOGY | + Feature.TREE_TOPOLOGY | + Feature.SET_TOPOLOGY | + Feature.DYNAMIC_POWER_FLOW + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +setmetatable(Feature, new_mt) + +return Feature + diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/types/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/types/init.lua new file mode 100644 index 0000000000..7609939803 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/PowerTopology/types/init.lua @@ -0,0 +1,14 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local types_mt = {} +types_mt.__index = function(self, key) + return require("embedded_clusters.PowerTopology.types." .. key) +end + +local PowerTopologyTypes = {} + +setmetatable(PowerTopologyTypes, types_mt) + +return PowerTopologyTypes + diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/init.lua index 696c324a81..be2141f09c 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/init.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/init.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local ValveConfigurationAndControlServerAttributes = require "embedded_clusters.ValveConfigurationAndControl.server.attributes" local ValveConfigurationAndControlServerCommands = require "embedded_clusters.ValveConfigurationAndControl.server.commands" diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/attributes/CurrentLevel.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/attributes/CurrentLevel.lua index 807beba485..c77f0f54ac 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/attributes/CurrentLevel.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/attributes/CurrentLevel.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/attributes/CurrentState.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/attributes/CurrentState.lua index 514659aa70..033d69e0a8 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/attributes/CurrentState.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/attributes/CurrentState.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/attributes/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/attributes/init.lua index dbffea4e8d..ddefc6b205 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/attributes/init.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/attributes/init.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/commands/Close.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/commands/Close.lua index baee7688ec..263bf0d849 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/commands/Close.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/commands/Close.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" local Close = {} diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/commands/Open.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/commands/Open.lua index 3f84040fc0..fd2f62801a 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/commands/Open.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/commands/Open.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" local Open = {} diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/commands/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/commands/init.lua index 74c402c3fd..86eb2b9546 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/commands/init.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/server/commands/init.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local command_mt = {} command_mt.__command_cache = {} command_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/types/Feature.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/types/Feature.lua index ea3ebeb7c1..bcbc7f0f38 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/types/Feature.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/types/Feature.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" local Feature = {} diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/types/ValveStateEnum.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/types/ValveStateEnum.lua index 8b6da46ad8..e445376bb8 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/types/ValveStateEnum.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/types/ValveStateEnum.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" local ValveStateEnum = {} diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/types/init.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/types/init.lua index 22aa362e99..ae8ebed286 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/types/init.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ValveConfigurationAndControl/types/init.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index bbf9ca24ee..6655f81e56 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local MatterDriver = require "st.matter.driver" local capabilities = require "st.capabilities" @@ -18,26 +7,29 @@ local device_lib = require "st.device" local clusters = require "st.matter.clusters" local log = require "log" local version = require "version" -local embedded_cluster_utils = require "utils.embedded_cluster_utils" - -local fields = require "utils.switch_fields" -local switch_utils = require "utils.switch_utils" -local cfg = require "utils.device_configuration" +local cfg = require "switch_utils.device_configuration" local device_cfg = cfg.DeviceCfg local switch_cfg = cfg.SwitchCfg local button_cfg = cfg.ButtonCfg - -local attribute_handlers = require "generic_handlers.attribute_handlers" -local event_handlers = require "generic_handlers.event_handlers" -local capability_handlers = require "generic_handlers.capability_handlers" +local fields = require "switch_utils.fields" +local switch_utils = require "switch_utils.utils" +local attribute_handlers = require "switch_handlers.attribute_handlers" +local event_handlers = require "switch_handlers.event_handlers" +local capability_handlers = require "switch_handlers.capability_handlers" +local embedded_cluster_utils = require "switch_utils.embedded_cluster_utils" -- Include driver-side definitions when lua libs api version is < 11 if version.api < 11 then clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement" clusters.ElectricalPowerMeasurement = require "embedded_clusters.ElectricalPowerMeasurement" + clusters.PowerTopology = require "embedded_clusters.PowerTopology" clusters.ValveConfigurationAndControl = require "embedded_clusters.ValveConfigurationAndControl" end +if version.api < 16 then + clusters.Descriptor = require "embedded_clusters.Descriptor" +end + local SwitchLifecycleHandlers = {} function SwitchLifecycleHandlers.device_added(driver, device) @@ -45,6 +37,8 @@ function SwitchLifecycleHandlers.device_added(driver, device) -- was created after the initial subscription report if device.network_type == device_lib.NETWORK_TYPE_CHILD then device:send(clusters.OnOff.attributes.OnOff:read(device)) + elseif device.network_type == device_lib.NETWORK_TYPE_MATTER then + switch_utils.handle_electrical_sensor_info(device) end -- call device init in case init is not called after added due to device caching @@ -53,28 +47,40 @@ end function SwitchLifecycleHandlers.do_configure(driver, device) if device.network_type == device_lib.NETWORK_TYPE_MATTER and not switch_utils.detect_bridge(device) then + switch_cfg.set_device_control_options(device) device_cfg.match_profile(driver, device) + elseif device.network_type == device_lib.NETWORK_TYPE_CHILD then + -- because get_parent_device() may cause race conditions if used in init, an initial child subscribe is handled in doConfigure. + -- all future calls to subscribe will be handled by the parent device in init + device:subscribe() end end function SwitchLifecycleHandlers.driver_switched(driver, device) if device.network_type == device_lib.NETWORK_TYPE_MATTER and not switch_utils.detect_bridge(device) then + switch_utils.handle_electrical_sensor_info(device) -- field settings required for proper setup when switching drivers device_cfg.match_profile(driver, device) end end function SwitchLifecycleHandlers.info_changed(driver, device, event, args) - if device.profile.id ~= args.old_st_store.profile.id then - device:subscribe() - local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}) - if #button_eps > 0 and device.network_type == device_lib.NETWORK_TYPE_MATTER then - button_cfg.configure_buttons(device) + if device.profile.id ~= args.old_st_store.profile.id or device:get_field(fields.MODULAR_PROFILE_UPDATED) then + device:set_field(fields.MODULAR_PROFILE_UPDATED, nil) + if device.network_type == device_lib.NETWORK_TYPE_MATTER then + device:subscribe() + button_cfg.configure_buttons(device, + device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}) + ) + elseif device.network_type == device_lib.NETWORK_TYPE_CHILD then + device:get_parent_device():subscribe() -- parent device required to send subscription requests end end -end -function SwitchLifecycleHandlers.device_removed(driver, device) - device.log.info("device removed") + if device.network_type == device_lib.NETWORK_TYPE_MATTER and not switch_utils.detect_bridge(device) then + if device.matter_version.software ~= args.old_st_store.matter_version.software then + device_cfg.match_profile(driver, device) + end + end end function SwitchLifecycleHandlers.device_init(driver, device) @@ -85,48 +91,22 @@ function SwitchLifecycleHandlers.device_init(driver, device) if device:get_field(fields.IS_PARENT_CHILD_DEVICE) then device:set_find_child(switch_utils.find_child) end - local main_endpoint = switch_utils.find_default_endpoint(device) - -- ensure subscription to all endpoint attributes- including those mapped to child devices - for idx, ep in ipairs(device.endpoints) do - if ep.endpoint_id ~= main_endpoint then - if device:supports_server_cluster(clusters.OnOff.ID, ep) then - local child_profile = switch_cfg.assign_child_profile(device, ep) - if idx == 1 and string.find(child_profile, "energy") then - -- when energy management is defined in the root endpoint(0), replace it with the first switch endpoint and process it. - device:set_field(fields.ENERGY_MANAGEMENT_ENDPOINT, ep, {persist = true}) - end - end - local id = 0 - for _, dt in ipairs(ep.device_types) do - id = math.max(id, dt.device_type_id) - end - for _, attr in pairs(fields.device_type_attribute_map[id] or {}) do - if id == fields.GENERIC_SWITCH_ID and - attr ~= clusters.PowerSource.attributes.BatPercentRemaining and - attr ~= clusters.PowerSource.attributes.BatChargeLevel then - device:add_subscribed_event(attr) - else - device:add_subscribed_attribute(attr) - end - end - end - end + device:extend_device("subscribe", switch_utils.subscribe) device:subscribe() -- device energy reporting must be handled cumulatively, periodically, or by both simulatanously. -- To ensure a single source of truth, we only handle a device's periodic reporting if cumulative reporting is not supported. - local electrical_energy_measurement_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID) - if #electrical_energy_measurement_eps > 0 then - local cumulative_energy_eps = embedded_cluster_utils.get_endpoints( - device, - clusters.ElectricalEnergyMeasurement.ID, - {feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY} - ) - if #cumulative_energy_eps == 0 then device:set_field(fields.CUMULATIVE_REPORTS_NOT_SUPPORTED, true, {persist = false}) end + if #embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID, + {feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY}) > 0 then + device:set_field(fields.CUMULATIVE_REPORTS_SUPPORTED, true, {persist = false}) end end end +function SwitchLifecycleHandlers.device_removed(driver, device) + device.log.info("device removed") +end + local matter_driver_template = { lifecycle_handlers = { added = SwitchLifecycleHandlers.device_added, @@ -149,9 +129,12 @@ local matter_driver_template = { [clusters.ColorControl.attributes.CurrentX.ID] = attribute_handlers.current_x_handler, [clusters.ColorControl.attributes.CurrentY.ID] = attribute_handlers.current_y_handler, }, + [clusters.Descriptor.ID] = { + [clusters.Descriptor.attributes.PartsList.ID] = attribute_handlers.parts_list_handler, + }, [clusters.ElectricalEnergyMeasurement.ID] = { - [clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported.ID] = attribute_handlers.energy_imported_factory(true), - [clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported.ID] = attribute_handlers.energy_imported_factory(false), + [clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported.ID] = attribute_handlers.energy_imported_factory(false), + [clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported.ID] = attribute_handlers.energy_imported_factory(true), }, [clusters.ElectricalPowerMeasurement.ID] = { [clusters.ElectricalPowerMeasurement.attributes.ActivePower.ID] = attribute_handlers.active_power_handler, @@ -180,6 +163,9 @@ local matter_driver_template = { [clusters.PowerSource.attributes.BatChargeLevel.ID] = attribute_handlers.bat_charge_level_handler, [clusters.PowerSource.attributes.BatPercentRemaining.ID] = attribute_handlers.bat_percent_remaining_handler, }, + [clusters.PowerTopology.ID] = { + [clusters.PowerTopology.attributes.AvailableEndpoints.ID] = attribute_handlers.available_endpoints_handler, + }, [clusters.RelativeHumidityMeasurement.ID] = { [clusters.RelativeHumidityMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.relative_humidity_measured_value_handler }, @@ -285,6 +271,9 @@ local matter_driver_template = { [capabilities.colorTemperature.ID] = { [capabilities.colorTemperature.commands.setColorTemperature.NAME] = capability_handlers.handle_set_color_temperature, }, + [capabilities.energyMeter.ID] = { + [capabilities.energyMeter.commands.resetEnergyMeter.NAME] = capability_handlers.handle_reset_energy_meter, + }, [capabilities.fanMode.ID] = { [capabilities.fanMode.commands.setFanMode.NAME] = capability_handlers.handle_set_fan_mode }, @@ -306,11 +295,46 @@ local matter_driver_template = { [capabilities.valve.commands.open.NAME] = capability_handlers.handle_valve_open, }, }, - supported_capabilities = fields.supported_capabilities, + supported_capabilities = { + capabilities.audioMute, + capabilities.audioRecording, + capabilities.audioVolume, + capabilities.battery, + capabilities.batteryLevel, + capabilities.button, + capabilities.cameraPrivacyMode, + capabilities.cameraViewportSettings, + capabilities.colorControl, + capabilities.colorTemperature, + capabilities.energyMeter, + capabilities.fanMode, + capabilities.fanSpeedPercent, + capabilities.hdr, + capabilities.illuminanceMeasurement, + capabilities.imageControl, + capabilities.level, + capabilities.localMediaStorage, + capabilities.mechanicalPanTiltZoom, + capabilities.motionSensor, + capabilities.nightVision, + capabilities.powerMeter, + capabilities.powerConsumptionReport, + capabilities.relativeHumidityMeasurement, + capabilities.sounds, + capabilities.switch, + capabilities.switchLevel, + capabilities.temperatureMeasurement, + capabilities.valve, + capabilities.videoStreamSettings, + capabilities.webrtc, + capabilities.zoneManagement + }, sub_drivers = { - require("sub_drivers.aqara_cube"), - require("sub_drivers.eve_energy"), - require("sub_drivers.third_reality_mk1") + switch_utils.lazy_load_if_possible("sub_drivers.aqara_cube"), + switch_utils.lazy_load("sub_drivers.camera"), + switch_utils.lazy_load_if_possible("sub_drivers.eve_energy"), + switch_utils.lazy_load_if_possible("sub_drivers.ikea_scroll"), + switch_utils.lazy_load_if_possible("sub_drivers.third_reality_mk1") } } diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/aqara_cube/can_handle.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/aqara_cube/can_handle.lua new file mode 100644 index 0000000000..dcecbc86ee --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/aqara_cube/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local device_lib = require "st.device" + +return function(opts, driver, device) + if device.network_type == device_lib.NETWORK_TYPE_MATTER then + local name = string.format("%s", device.manufacturer_info.product_name) + if string.find(name, "Aqara Cube T1 Pro") then + return true, require("sub_drivers.aqara_cube") + end + end + return false +end diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/aqara_cube/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/aqara_cube/init.lua index 455bac34a4..4e6b729fa3 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/aqara_cube/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/aqara_cube/init.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" @@ -32,16 +21,6 @@ local INITIAL_PRESS_ONLY = "__initial_press_only" -- for devices that support MS local CUBEACTION_TIMER = "__cubeAction_timer" local CUBEACTION_TIME = 3 -local function is_aqara_cube(opts, driver, device) - if device.network_type == device_lib.NETWORK_TYPE_MATTER then - local name = string.format("%s", device.manufacturer_info.product_name) - if string.find(name, "Aqara Cube T1 Pro") then - return true - end - end - return false -end - local callback_timer = function(device) return function() device:emit_event(cubeAction.cubeAction("noAction")) @@ -251,7 +230,7 @@ local aqara_cube_handler = { } }, }, - can_handle = is_aqara_cube + can_handle = require("sub_drivers.aqara_cube.can_handle") } return aqara_cube_handler diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/attribute_handlers.lua new file mode 100644 index 0000000000..484e4c7a15 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/attribute_handlers.lua @@ -0,0 +1,419 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local camera_fields = require "sub_drivers.camera.camera_utils.fields" +local camera_utils = require "sub_drivers.camera.camera_utils.utils" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local camera_cfg = require "sub_drivers.camera.camera_utils.device_configuration" +local fields = require "switch_utils.fields" +local utils = require "st.utils" + +local CameraAttributeHandlers = {} + +CameraAttributeHandlers.enabled_state_factory = function(attribute) + return function(driver, device, ib, response) + device:emit_event_for_endpoint(ib, attribute(ib.data.value and "enabled" or "disabled")) + if attribute == capabilities.imageControl.imageFlipHorizontal then + camera_utils.update_supported_attributes(device, ib, capabilities.imageControl, "imageFlipHorizontal") + elseif attribute == capabilities.imageControl.imageFlipVertical then + camera_utils.update_supported_attributes(device, ib, capabilities.imageControl, "imageFlipVertical") + elseif attribute == capabilities.cameraPrivacyMode.hardPrivacyMode then + camera_utils.update_supported_attributes(device, ib, capabilities.cameraPrivacyMode, "hardPrivacyMode") + end + end +end + +CameraAttributeHandlers.night_vision_factory = function(attribute) + return function(driver, device, ib, response) + if camera_fields.tri_state_map[ib.data.value] then + device:emit_event_for_endpoint(ib, attribute(camera_fields.tri_state_map[ib.data.value])) + if attribute == capabilities.nightVision.illumination then + local _ = device:get_latest_state(camera_fields.profile_components.main, capabilities.nightVision.ID, capabilities.nightVision.supportedAttributes.NAME) or + device:emit_event_for_endpoint(ib, capabilities.nightVision.supportedAttributes({"illumination"})) + end + end + end +end + +function CameraAttributeHandlers.image_rotation_handler(driver, device, ib, response) + local degrees = utils.clamp_value(ib.data.value, 0, 359) + device:emit_event_for_endpoint(ib, capabilities.imageControl.imageRotation(degrees)) + camera_utils.update_supported_attributes(device, ib, capabilities.imageControl, "imageRotation") +end + +function CameraAttributeHandlers.two_way_talk_support_handler(driver, device, ib, response) + local two_way_talk_supported = ib.data.value == clusters.CameraAvStreamManagement.types.TwoWayTalkSupportTypeEnum.HALF_DUPLEX or + ib.data.value == clusters.CameraAvStreamManagement.types.TwoWayTalkSupportTypeEnum.FULL_DUPLEX + device:emit_event_for_endpoint(ib, capabilities.webrtc.talkback(two_way_talk_supported)) + if two_way_talk_supported then + device:emit_event_for_endpoint(ib, capabilities.webrtc.talkbackDuplex( + ib.data.value == clusters.CameraAvStreamManagement.types.TwoWayTalkSupportTypeEnum.HALF_DUPLEX and "halfDuplex" or "fullDuplex" + )) + end +end + +function CameraAttributeHandlers.muted_handler(driver, device, ib, response) + device:emit_event_for_endpoint(ib, capabilities.audioMute.mute(ib.data.value and "muted" or "unmuted")) +end + +function CameraAttributeHandlers.volume_level_handler(driver, device, ib, response) + local component = device:endpoint_to_component(ib) + local max_volume = device:get_field(camera_fields.MAX_VOLUME_LEVEL .. "_" .. component) or camera_fields.ABS_VOL_MAX + local min_volume = device:get_field(camera_fields.MIN_VOLUME_LEVEL .. "_" .. component) or camera_fields.ABS_VOL_MIN + -- Convert from [min_volume, max_volume] to [0, 100] before emitting capability + local limited_range = max_volume - min_volume + local normalized_volume = utils.round((ib.data.value - min_volume) * 100.0 / limited_range) + device:emit_event_for_endpoint(ib, capabilities.audioVolume.volume(normalized_volume)) +end + +function CameraAttributeHandlers.max_volume_level_handler(driver, device, ib, response) + local component = device:endpoint_to_component(ib) + local max_volume = ib.data.value + local min_volume = device:get_field(camera_fields.MIN_VOLUME_LEVEL .. "_" .. component) + if max_volume > camera_fields.ABS_VOL_MAX or (min_volume and max_volume <= min_volume) then + device.log.warn(string.format("Device reported invalid maximum (%d) %s volume level range value", ib.data.value, component)) + max_volume = camera_fields.ABS_VOL_MAX + end + device:set_field(camera_fields.MAX_VOLUME_LEVEL .. "_" .. component, max_volume) +end + +function CameraAttributeHandlers.min_volume_level_handler(driver, device, ib, response) + local component = device:endpoint_to_component(ib) + local min_volume = ib.data.value + local max_volume = device:get_field(camera_fields.MAX_VOLUME_LEVEL .. "_" .. component) + if min_volume < camera_fields.ABS_VOL_MIN or (max_volume and min_volume >= max_volume) then + device.log.warn(string.format("Device reported invalid minimum (%d) %s volume level range value", ib.data.value, component)) + min_volume = camera_fields.ABS_VOL_MIN + end + device:set_field(camera_fields.MIN_VOLUME_LEVEL .. "_" .. component, min_volume) +end + +function CameraAttributeHandlers.status_light_enabled_handler(driver, device, ib, response) + device:emit_event_for_endpoint(ib, ib.data.value and capabilities.switch.switch.on() or capabilities.switch.switch.off()) +end + +function CameraAttributeHandlers.status_light_brightness_handler(driver, device, ib, response) + local component = device:endpoint_to_component(ib) + local _ = device:get_latest_state(component, capabilities.mode.ID, capabilities.mode.supportedModes.NAME) or + device:emit_event_for_endpoint(ib, capabilities.mode.supportedModes({"low", "medium", "high", "auto"}, {visibility = {displayed = false}})) + local _ = device:get_latest_state(component, capabilities.mode.ID, capabilities.mode.supportedArguments.NAME) or + device:emit_event_for_endpoint(ib, capabilities.mode.supportedArguments({"low", "medium", "high", "auto"}, {visibility = {displayed = false}})) + local mode = "auto" + if ib.data.value == clusters.Global.types.ThreeLevelAutoEnum.LOW then + mode = "low" + elseif ib.data.value == clusters.Global.types.ThreeLevelAutoEnum.MEDIUM then + mode = "medium" + elseif ib.data.value == clusters.Global.types.ThreeLevelAutoEnum.HIGH then + mode = "high" + end + device:emit_event_for_endpoint(ib, capabilities.mode.mode(mode)) +end + +function CameraAttributeHandlers.rate_distortion_trade_off_points_handler(driver, device, ib, response) + if not ib.data.elements then return end + local resolutions = {} + local max_encoded_pixel_rate = device:get_field(camera_fields.MAX_ENCODED_PIXEL_RATE) + local max_fps = device:get_field(camera_fields.MAX_FRAMES_PER_SECOND) + local emit_capability = max_encoded_pixel_rate ~= nil and max_fps ~= nil + for _, v in ipairs(ib.data.elements) do + local rate_distortion_trade_off_points = v.elements + local width = rate_distortion_trade_off_points.resolution.elements.width.value + local height = rate_distortion_trade_off_points.resolution.elements.height.value + table.insert(resolutions, { + width = width, + height = height + }) + if emit_capability then + local fps = camera_utils.compute_fps(max_encoded_pixel_rate, width, height, max_fps) + if fps > 0 then + resolutions[#resolutions].fps = fps + end + end + end + if emit_capability then + device:emit_event_for_endpoint(ib, capabilities.videoStreamSettings.supportedResolutions(resolutions)) + end + device:set_field(camera_fields.SUPPORTED_RESOLUTIONS, resolutions) +end + +function CameraAttributeHandlers.max_encoded_pixel_rate_handler(driver, device, ib, response) + local resolutions = device:get_field(camera_fields.SUPPORTED_RESOLUTIONS) + local max_fps = device:get_field(camera_fields.MAX_FRAMES_PER_SECOND) + local emit_capability = resolutions ~= nil and max_fps ~= nil + if emit_capability then + for _, v in pairs(resolutions or {}) do + local fps = camera_utils.compute_fps(ib.data.value, v.width, v.height, max_fps) + if fps > 0 then + v.fps = fps + end + end + device:emit_event_for_endpoint(ib, capabilities.videoStreamSettings.supportedResolutions(resolutions)) + end + device:set_field(camera_fields.MAX_ENCODED_PIXEL_RATE, ib.data.value) +end + +function CameraAttributeHandlers.video_sensor_parameters_handler(driver, device, ib, response) + if not ib.data.elements then return end + local resolutions = device:get_field(camera_fields.SUPPORTED_RESOLUTIONS) + local max_encoded_pixel_rate = device:get_field(camera_fields.MAX_ENCODED_PIXEL_RATE) + local emit_capability = resolutions ~= nil and max_encoded_pixel_rate ~= nil + local sensor_width, sensor_height, max_fps + for _, v in pairs(ib.data.elements) do + if v.field_id == 0 then + sensor_width = v.value + elseif v.field_id == 1 then + sensor_height = v.value + elseif v.field_id == 2 then + max_fps = v.value + end + end + + if max_fps then + if sensor_width and sensor_height then + device:emit_event_for_endpoint(ib, capabilities.cameraViewportSettings.videoSensorParameters({ + width = sensor_width, + height = sensor_height, + maxFPS = max_fps + })) + end + if emit_capability then + for _, v in pairs(resolutions or {}) do + local fps = camera_utils.compute_fps(max_encoded_pixel_rate, v.width, v.height, max_fps) + if fps > 0 then + v.fps = fps + end + end + device:emit_event_for_endpoint(ib, capabilities.videoStreamSettings.supportedResolutions(resolutions)) + end + device:set_field(camera_fields.MAX_FRAMES_PER_SECOND, max_fps) + end +end + +function CameraAttributeHandlers.min_viewport_handler(driver, device, ib, response) + device:emit_event_for_endpoint(ib, capabilities.cameraViewportSettings.minViewportResolution({ + width = ib.data.elements.width.value, + height = ib.data.elements.height.value + })) +end + +function CameraAttributeHandlers.allocated_video_streams_handler(driver, device, ib, response) + if not ib.data.elements then return end + local streams = {} + for i, v in ipairs(ib.data.elements) do + local stream = v.elements + local video_stream = { + streamId = stream.video_stream_id.value, + data = { + label = "Stream " .. i, + type = stream.stream_usage.value == clusters.Global.types.StreamUsageEnum.LIVE_VIEW and "liveStream" or "clipRecording", + resolution = { + width = stream.min_resolution.elements.width.value, + height = stream.min_resolution.elements.height.value, + fps = stream.min_frame_rate.value + } + } + } + local viewport = device:get_field(camera_fields.VIEWPORT) + if viewport then + video_stream.data.viewport = viewport + end + if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.WATERMARK) then + video_stream.data.watermark = stream.watermark_enabled.value and "enabled" or "disabled" + end + if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.ON_SCREEN_DISPLAY) then + video_stream.data.onScreenDisplay = stream.osd_enabled.value and "enabled" or "disabled" + end + table.insert(streams, video_stream) + end + if #streams > 0 then + device:emit_event_for_endpoint(ib, capabilities.videoStreamSettings.videoStreams(streams)) + end +end + +function CameraAttributeHandlers.viewport_handler(driver, device, ib, response) + device:emit_event_for_endpoint(ib, capabilities.cameraViewportSettings.defaultViewport({ + upperLeftVertex = { x = ib.data.elements.x1.value, y = ib.data.elements.y1.value }, + lowerRightVertex = { x = ib.data.elements.x2.value, y = ib.data.elements.y2.value }, + })) +end + +function CameraAttributeHandlers.ptz_position_handler(driver, device, ib, response) + local ptz_map = camera_utils.get_ptz_map(device) + local emit_event = function(idx, value) + if value ~= ptz_map[idx].current then + device:emit_event_for_endpoint(ib, ptz_map[idx].attribute( + utils.clamp_value(value, ptz_map[idx].range.minimum, ptz_map[idx].range.maximum) + )) + end + end + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MPAN) then + emit_event(camera_fields.PAN_IDX, ib.data.elements.pan.value) + end + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MTILT) then + emit_event(camera_fields.TILT_IDX, ib.data.elements.tilt.value) + end + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MZOOM) then + emit_event(camera_fields.ZOOM_IDX, ib.data.elements.zoom.value) + end +end + +function CameraAttributeHandlers.ptz_presets_handler(driver, device, ib, response) + if not ib.data.elements then return end + local presets = {} + for _, v in ipairs(ib.data.elements) do + local preset = v.elements + local pan, tilt, zoom = 0, 0, 1 + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MPAN) then + pan = preset.settings.elements.pan.value + end + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MTILT) then + tilt = preset.settings.elements.tilt.value + end + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MZOOM) then + zoom = preset.settings.elements.zoom.value + end + table.insert(presets, { id = preset.preset_id.value, label = preset.name.value, pan = pan, tilt = tilt, zoom = zoom }) + end + device:emit_event_for_endpoint(ib, capabilities.mechanicalPanTiltZoom.presets(presets)) +end + +function CameraAttributeHandlers.max_presets_handler(driver, device, ib, response) + device:emit_event_for_endpoint(ib, capabilities.mechanicalPanTiltZoom.maxPresets(ib.data.value)) +end + +function CameraAttributeHandlers.zoom_max_handler(driver, device, ib, response) + if ib.data.value <= camera_fields.ABS_ZOOM_MAX then + device:emit_event_for_endpoint(ib, capabilities.mechanicalPanTiltZoom.zoomRange({ value = { minimum = 1, maximum = ib.data.value } })) + else + device.log.warn(string.format("Device reported invalid maximum zoom (%d)", ib.data.value)) + end +end + +CameraAttributeHandlers.pt_range_handler_factory = function(attribute, limit_field) + return function(driver, device, ib, response) + device:set_field(limit_field, ib.data.value) + local field = string.find(limit_field, "PAN") and "PAN" or "TILT" + local min = device:get_field(camera_fields.pt_range_fields[field].min) + local max = device:get_field(camera_fields.pt_range_fields[field].max) + if min ~= nil and max ~= nil then + local abs_min = field == "PAN" and camera_fields.ABS_PAN_MIN or camera_fields.ABS_TILT_MIN + local abs_max = field == "PAN" and camera_fields.ABS_PAN_MAX or camera_fields.ABS_TILT_MAX + if min < max and min >= abs_min and max <= abs_max then + device:emit_event_for_endpoint(ib, attribute({ value = { minimum = min, maximum = max } })) + device:set_field(camera_fields.pt_range_fields[field].min, nil) + device:set_field(camera_fields.pt_range_fields[field].max, nil) + else + device.log.warn(string.format("Device reported invalid minimum (%d) and maximum (%d) %s " .. + "range values (should be between %d and %d)", min, max, string.lower(field), abs_min, abs_max)) + end + end + end +end + +function CameraAttributeHandlers.max_zones_handler(driver, device, ib, response) + device:emit_event_for_endpoint(ib, capabilities.zoneManagement.maxZones(ib.data.value)) +end + +function CameraAttributeHandlers.zones_handler(driver, device, ib, response) + if not ib.data.elements then return end + local zones = {} + for _, v in ipairs(ib.data.elements) do + local zone = v.elements + local zone_id = zone.zone_id.value + local zone_type = zone.zone_type.value + local zone_source = zone.zone_source.value + local zone_vertices = {} + if camera_utils.feature_supported(device, clusters.ZoneManagement.ID, clusters.ZoneManagement.types.Feature.TWO_DIMENSIONAL_CARTESIAN_ZONE) and + zone_type == clusters.ZoneManagement.types.ZoneTypeEnum.TWODCART_ZONE then + local zone_name = zone.two_d_cartesian_zone.elements.name.value + local zone_use = zone.two_d_cartesian_zone.elements.use.value + for _, vertex in pairs(zone.two_d_cartesian_zone.elements.vertices.elements or {}) do + table.insert(zone_vertices, {vertex = {x = vertex.elements.x.value, y = vertex.elements.y.value}}) + end + local zone_uses = { + [clusters.ZoneManagement.types.ZoneUseEnum.MOTION] = "motion", + [clusters.ZoneManagement.types.ZoneUseEnum.FOCUS] = "focus", + [clusters.ZoneManagement.types.ZoneUseEnum.PRIVACY] = "privacy" + } + local zone_color = zone.two_d_cartesian_zone.elements.color and zone.two_d_cartesian_zone.elements.color.value or nil + table.insert(zones, { + id = zone_id, + name = zone_name, + type = "2DCartesian", + polygonVertices = zone_vertices, + source = zone_source == clusters.ZoneManagement.types.ZoneSourceEnum.MFG and "manufacturer" or "user", + use = zone_uses[zone_use], + color = zone_color + }) + else + device.log.warn(string.format("Zone type not currently supported: (%s)", zone_type)) + end + end + device:emit_event_for_endpoint(ib, capabilities.zoneManagement.zones({value = zones})) +end + +function CameraAttributeHandlers.triggers_handler(driver, device, ib, response) + if not ib.data.elements then return end + local triggers = {} + for _, v in ipairs(ib.data.elements) do + local trigger = v.elements + table.insert(triggers, { + zoneId = trigger.zone_id.value, + initialDuration = trigger.initial_duration.value, + augmentationDuration = trigger.augmentation_duration.value, + maxDuration = trigger.max_duration.value, + blindDuration = trigger.blind_duration.value, + sensitivity = camera_utils.feature_supported(device, clusters.ZoneManagement.ID, clusters.ZoneManagement.types.Feature.PER_ZONE_SENSITIVITY) and trigger.sensitivity.value + }) + end + device:emit_event_for_endpoint(ib, capabilities.zoneManagement.triggers(triggers)) +end + +function CameraAttributeHandlers.sensitivity_max_handler(driver, device, ib, response) + device:emit_event_for_endpoint(ib, capabilities.zoneManagement.sensitivityRange({minimum = 1, maximum = ib.data.value}, + {visibility = {displayed = false}})) +end + +function CameraAttributeHandlers.sensitivity_handler(driver, device, ib, response) + device:emit_event_for_endpoint(ib, capabilities.zoneManagement.sensitivity(ib.data.value, {visibility = {displayed = false}})) +end + +function CameraAttributeHandlers.installed_chime_sounds_handler(driver, device, ib, response) + if not ib.data.elements then return end + local installed_chimes = {} + for _, v in ipairs(ib.data.elements) do + local chime = v.elements + table.insert(installed_chimes, {id = chime.chime_id.value, label = chime.name.value}) + end + device:emit_event_for_endpoint(ib, capabilities.sounds.supportedSounds(installed_chimes, {visibility = {displayed = false}})) +end + +function CameraAttributeHandlers.selected_chime_handler(driver, device, ib, response) + device:emit_event_for_endpoint(ib, capabilities.sounds.selectedSound(ib.data.value)) +end + +function CameraAttributeHandlers.camera_av_stream_management_attribute_list_handler(driver, device, ib, response) + if not ib.data.elements then return end + local status_light_enabled_present, status_light_brightness_present = false, false + local attribute_ids = {} + for _, attr in ipairs(ib.data.elements) do + if attr.value == clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID then + status_light_enabled_present = true + table.insert(attribute_ids, clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID) + elseif attr.value == clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID then + status_light_brightness_present = true + table.insert(attribute_ids, clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID) + end + end + local component_map = device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) or {} + component_map.statusLed = { + endpoint_id = ib.endpoint_id, + cluster_id = ib.cluster_id, + attribute_ids = attribute_ids, + } + device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, component_map, {persist=true}) + camera_cfg.match_profile(device, status_light_enabled_present, status_light_brightness_present) +end + +return CameraAttributeHandlers \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/capability_handlers.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/capability_handlers.lua new file mode 100644 index 0000000000..fb85eb863f --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/capability_handlers.lua @@ -0,0 +1,356 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local camera_fields = require "sub_drivers.camera.camera_utils.fields" +local camera_utils = require "sub_drivers.camera.camera_utils.utils" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local utils = require "st.utils" + +local CameraCapabilityHandlers = {} + +CameraCapabilityHandlers.set_enabled_factory = function(attribute) + return function(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(attribute:write(device, endpoint_id, cmd.args.state == "enabled")) + end +end + +CameraCapabilityHandlers.set_night_vision_factory = function(attribute) + return function(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + for i, v in pairs(camera_fields.tri_state_map) do + if v == cmd.args.mode then + device:send(attribute:write(device, endpoint_id, i)) + return + end + end + device.log.warn(string.format("Capability command sent with unknown value: (%s)", cmd.args.mode)) + end +end + +function CameraCapabilityHandlers.handle_set_image_rotation(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local degrees = utils.clamp_value(cmd.args.rotation, 0, 359) + device:send(clusters.CameraAvStreamManagement.attributes.ImageRotation:write(device, endpoint_id, degrees)) +end + +CameraCapabilityHandlers.handle_mute_commands_factory = function(command) + return function(driver, device, cmd) + local attr + if cmd.component == camera_fields.profile_components.speaker then + attr = clusters.CameraAvStreamManagement.attributes.SpeakerMuted + elseif cmd.component == camera_fields.profile_components.microphone then + attr = clusters.CameraAvStreamManagement.attributes.MicrophoneMuted + else + device.log.warn(string.format("Capability command sent from unknown component: (%s)", cmd.component)) + return + end + local endpoint_id = device:component_to_endpoint(cmd.component) + local mute_state = false + if command == capabilities.audioMute.commands.setMute.NAME then + mute_state = cmd.args.state == "muted" + elseif command == capabilities.audioMute.commands.mute.NAME then + mute_state = true + end + device:send(attr:write(device, endpoint_id, mute_state)) + end +end + +function CameraCapabilityHandlers.handle_set_volume(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local max_volume = device:get_field(camera_fields.MAX_VOLUME_LEVEL .. "_" .. cmd.component) or camera_fields.ABS_VOL_MAX + local min_volume = device:get_field(camera_fields.MIN_VOLUME_LEVEL .. "_" .. cmd.component) or camera_fields.ABS_VOL_MIN + -- Convert from [0, 100] to [min_volume, max_volume] before writing attribute + local volume_range = max_volume - min_volume + local volume = utils.round(cmd.args.volume * volume_range / 100.0 + min_volume) + if cmd.component == camera_fields.profile_components.speaker then + device:send(clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel:write(device, endpoint_id, volume)) + elseif cmd.component == camera_fields.profile_components.microphone then + device:send(clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel:write(device, endpoint_id, volume)) + else + device.log.warn(string.format("Capability command sent from unknown component: (%s)", cmd.component)) + end +end + +function CameraCapabilityHandlers.handle_volume_up(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local max_volume = device:get_field(camera_fields.MAX_VOLUME_LEVEL .. "_" .. cmd.component) or camera_fields.ABS_VOL_MAX + local min_volume = device:get_field(camera_fields.MIN_VOLUME_LEVEL .. "_" .. cmd.component) or camera_fields.ABS_VOL_MIN + local volume = device:get_latest_state(cmd.component, capabilities.audioVolume.ID, capabilities.audioVolume.volume.NAME) + if not volume or volume >= max_volume then return end + -- Convert from [0, 100] to [min_volume, max_volume] before writing attribute + local volume_range = max_volume - min_volume + local converted_volume = utils.round((volume + 1) * volume_range / 100.0 + min_volume) + if cmd.component == camera_fields.profile_components.speaker then + device:send(clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel:write(device, endpoint_id, converted_volume)) + elseif cmd.component == camera_fields.profile_components.microphone then + device:send(clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel:write(device, endpoint_id, converted_volume)) + end +end + +function CameraCapabilityHandlers.handle_volume_down(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local max_volume = device:get_field(camera_fields.MAX_VOLUME_LEVEL .. "_" .. cmd.component) or camera_fields.ABS_VOL_MAX + local min_volume = device:get_field(camera_fields.MIN_VOLUME_LEVEL .. "_" .. cmd.component) or camera_fields.ABS_VOL_MIN + local volume = device:get_latest_state(cmd.component, capabilities.audioVolume.ID, capabilities.audioVolume.volume.NAME) + if not volume or volume <= min_volume then return end + -- Convert from [0, 100] to [min_volume, max_volume] before writing attribute + local volume_range = max_volume - min_volume + local converted_volume = utils.round((volume - 1) * volume_range / 100.0 + min_volume) + if cmd.component == camera_fields.profile_components.speaker then + device:send(clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel:write(device, endpoint_id, converted_volume)) + elseif cmd.component == camera_fields.profile_components.microphone then + device:send(clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel:write(device, endpoint_id, converted_volume)) + end +end + +function CameraCapabilityHandlers.handle_set_status_light_mode(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local level_auto_value + if cmd.args.mode == "low" then level_auto_value = "LOW" + elseif cmd.args.mode == "medium" then level_auto_value = "MEDIUM" + elseif cmd.args.mode == "high" then level_auto_value = "HIGH" + elseif cmd.args.mode == "auto" then level_auto_value = "AUTO" end + if not level_auto_value then + device.log.warn(string.format("Invalid mode received from setMode command: %s", cmd.args.mode)) + return + end + device:send(clusters.CameraAvStreamManagement.attributes.StatusLightBrightness:write(device, endpoint_id, + clusters.Global.types.ThreeLevelAutoEnum[level_auto_value])) +end + +function CameraCapabilityHandlers.handle_status_led_on(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.CameraAvStreamManagement.attributes.StatusLightEnabled:write(device, endpoint_id, true)) +end + +function CameraCapabilityHandlers.handle_status_led_off(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.CameraAvStreamManagement.attributes.StatusLightEnabled:write(device, endpoint_id, false)) +end + +function CameraCapabilityHandlers.handle_audio_recording(driver, device, cmd) + -- TODO: Allocate audio stream if it doesn't exist + local component = device.profile.components[cmd.component] + device:emit_component_event(component, capabilities.audioRecording.audioRecording(cmd.args.state)) +end + +CameraCapabilityHandlers.ptz_relative_move_factory = function(index) + return function (driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local pan_delta = index == camera_fields.PAN_IDX and cmd.args.delta or 0 + local tilt_delta = index == camera_fields.TILT_IDX and cmd.args.delta or 0 + local zoom_delta = index == camera_fields.ZOOM_IDX and cmd.args.delta or 0 + device:send(clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove( + device, endpoint_id, pan_delta, tilt_delta, zoom_delta + )) + end +end + +CameraCapabilityHandlers.ptz_set_position_factory = function(command) + return function (driver, device, cmd) + local ptz_map = camera_utils.get_ptz_map(device) + if command == capabilities.mechanicalPanTiltZoom.commands.setPanTiltZoom then + ptz_map[camera_fields.PAN_IDX].current = cmd.args.pan + ptz_map[camera_fields.TILT_IDX].current = cmd.args.tilt + ptz_map[camera_fields.ZOOM_IDX].current = cmd.args.zoom + elseif command == capabilities.mechanicalPanTiltZoom.commands.setPan then + ptz_map[camera_fields.PAN_IDX].current = cmd.args.pan + elseif command == capabilities.mechanicalPanTiltZoom.commands.setTilt then + ptz_map[camera_fields.TILT_IDX].current = cmd.args.tilt + else + ptz_map[camera_fields.ZOOM_IDX].current = cmd.args.zoom + end + for _, v in pairs(ptz_map) do + v.current = utils.clamp_value(v.current, v.range.minimum, v.range.maximum) + end + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZSetPosition(device, endpoint_id, + ptz_map[camera_fields.PAN_IDX].current, ptz_map[camera_fields.TILT_IDX].current, ptz_map[camera_fields.ZOOM_IDX].current + )) + end +end + +function CameraCapabilityHandlers.handle_save_preset(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZSavePreset( + device, endpoint_id, cmd.args.id, cmd.args.label + )) +end + +function CameraCapabilityHandlers.handle_remove_preset(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRemovePreset(device, endpoint_id, cmd.args.id)) +end + +function CameraCapabilityHandlers.handle_move_to_preset(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZMoveToPreset(device, endpoint_id, cmd.args.id)) +end + +function CameraCapabilityHandlers.handle_new_zone(driver, device, cmd) + local zone_uses = { + ["motion"] = clusters.ZoneManagement.types.ZoneUseEnum.MOTION, + ["focus"] = camera_utils.feature_supported(device, clusters.ZoneManagement.ID, clusters.ZoneManagement.types.Feature.FOCUSZONES) and + clusters.ZoneManagement.types.ZoneUseEnum.FOCUS or clusters.ZoneManagement.types.ZoneUseEnum.PRIVACY, + ["privacy"] = clusters.ZoneManagement.types.ZoneUseEnum.PRIVACY + } + local vertices = {} + for _, v in pairs(cmd.args.polygonVertices or {}) do + table.insert(vertices, clusters.ZoneManagement.types.TwoDCartesianVertexStruct({x = v.value.x, y = v.value.y})) + end + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.ZoneManagement.server.commands.CreateTwoDCartesianZone( + device, endpoint_id, clusters.ZoneManagement.types.TwoDCartesianZoneStruct( + { + name = cmd.args.name, + use = zone_uses[cmd.args.use], + vertices = vertices, + color = cmd.args.color + } + ) + )) +end + +function CameraCapabilityHandlers.handle_update_zone(driver, device, cmd) + local zone_uses = { + ["motion"] = clusters.ZoneManagement.types.ZoneUseEnum.MOTION, + ["focus"] = camera_utils.feature_supported(device, clusters.ZoneManagement.ID, clusters.ZoneManagement.types.Feature.FOCUSZONES) and + clusters.ZoneManagement.types.ZoneUseEnum.FOCUS or clusters.ZoneManagement.types.ZoneUseEnum.PRIVACY, + ["privacy"] = clusters.ZoneManagement.types.ZoneUseEnum.PRIVACY + } + if not cmd.args.name or not cmd.args.polygonVertices or not cmd.args.use or not cmd.args.color then + local zones = device:get_latest_state( + camera_fields.profile_components.main, capabilities.zoneManagement.ID, capabilities.zoneManagement.zones.NAME + ) or {} + local found_zone = false + for _, v in pairs(zones) do + if v.id == cmd.args.zoneId then + if not cmd.args.name then cmd.args.name = v.name end + if not cmd.args.polygonVertices then cmd.args.polygonVertices = v.polygonVertices end + if not cmd.args.use then cmd.args.use = v.use end + if not cmd.args.color then cmd.args.color = v.color end -- color may be nil, but it is optional in TwoDCartesianZoneStruct + found_zone = true + break + end + end + if not found_zone then + device.log.warn_with({hub_logs = true}, string.format("Zone does not exist, cannot update the zone.")) + return + end + end + local vertices = {} + for _, v in pairs(cmd.args.polygonVertices or {}) do + table.insert(vertices, clusters.ZoneManagement.types.TwoDCartesianVertexStruct({x = v.value.x, y = v.value.y})) + end + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.ZoneManagement.server.commands.UpdateTwoDCartesianZone( + device, endpoint_id, cmd.args.zoneId, clusters.ZoneManagement.types.TwoDCartesianZoneStruct( + { + name = cmd.args.name, + use = zone_uses[cmd.args.use], + vertices = vertices, + color = cmd.args.color + } + ) + )) +end + +function CameraCapabilityHandlers.handle_remove_zone(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.ZoneManagement.server.commands.RemoveZone(device, endpoint_id, cmd.args.zoneId)) +end + +function CameraCapabilityHandlers.handle_create_or_update_trigger(driver, device, cmd) + if not cmd.args.augmentationDuration or not cmd.args.maxDuration or not cmd.args.blindDuration or + (camera_utils.feature_supported(device, clusters.ZoneManagement.ID, clusters.ZoneManagement.types.Feature.PER_ZONE_SENSITIVITY) and + not cmd.args.sensitivity) then + local triggers = device:get_latest_state( + camera_fields.profile_components.main, capabilities.zoneManagement.ID, capabilities.zoneManagement.triggers.NAME + ) or {} + local found_trigger = false + for _, v in pairs(triggers) do + if v.zoneId == cmd.args.zoneId then + if not cmd.args.augmentationDuration then cmd.args.augmentationDuration = v.augmentationDuration end + if not cmd.args.maxDuration then cmd.args.maxDuration = v.maxDuration end + if not cmd.args.blindDuration then cmd.args.blindDuration = v.blindDuration end + if camera_utils.feature_supported(device, clusters.ZoneManagement.ID, clusters.ZoneManagement.types.Feature.PER_ZONE_SENSITIVITY) and + not cmd.args.sensitivity then + cmd.args.sensitivity = v.sensitivity + end + found_trigger = true + break + end + end + if not found_trigger then + device.log.warn_with({hub_logs = true}, string.format("Missing fields needed to create trigger.")) + return + end + end + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.ZoneManagement.server.commands.CreateOrUpdateTrigger( + device, endpoint_id, clusters.ZoneManagement.types.ZoneTriggerControlStruct( + { + zone_id = cmd.args.zoneId, + initial_duration = cmd.args.initialDuration, + augmentation_duration = cmd.args.augmentationDuration, + max_duration = cmd.args.maxDuration, + blind_duration = cmd.args.blindDuration, + sensitivity = cmd.args.sensitivity + } + ) + )) +end + +function CameraCapabilityHandlers.handle_remove_trigger(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.ZoneManagement.server.commands.RemoveTrigger(device, endpoint_id, cmd.args.zoneId)) +end + +function CameraCapabilityHandlers.handle_set_sensitivity(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + if not camera_utils.feature_supported(device, clusters.ZoneManagement.ID, clusters.ZoneManagement.types.Feature.PER_ZONE_SENSITIVITY) then + device:send(clusters.ZoneManagement.attributes.Sensitivity:write(device, endpoint_id, cmd.args.id)) + else + device.log.warn(string.format("Can't set global zone sensitivity setting, per zone sensitivity enabled.")) + end +end + +function CameraCapabilityHandlers.handle_play_sound(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.Chime.server.commands.PlayChimeSound(device, endpoint_id)) +end + +function CameraCapabilityHandlers.handle_set_selected_sound(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.Chime.attributes.SelectedChime:write(device, endpoint_id, cmd.args.id)) +end + +function CameraCapabilityHandlers.handle_set_stream(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local watermark_enabled, on_screen_display_enabled + if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.WATERMARK) then + watermark_enabled = cmd.args.watermark == "enabled" + end + if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.ON_SCREEN_DISPLAY) then + on_screen_display_enabled = cmd.args.onScreenDisplay == "enabled" + end + device:send(clusters.CameraAvStreamManagement.server.commands.VideoStreamModify(device, endpoint_id, + cmd.args.streamId, watermark_enabled, on_screen_display_enabled + )) +end + +function CameraCapabilityHandlers.handle_set_default_viewport(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.CameraAvStreamManagement.attributes.Viewport:write( + device, endpoint_id, clusters.Global.types.ViewportStruct({ + x1 = cmd.args.upperLeftVertex.x, + x2 = cmd.args.lowerRightVertex.x, + y1 = cmd.args.upperLeftVertex.y, + y2 = cmd.args.lowerRightVertex.y + }) + )) +end + +return CameraCapabilityHandlers diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/event_handlers.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/event_handlers.lua new file mode 100644 index 0000000000..02b63bb37f --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/event_handlers.lua @@ -0,0 +1,30 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local camera_fields = require "sub_drivers.camera.camera_utils.fields" +local capabilities = require "st.capabilities" +local switch_utils = require "switch_utils.utils" + +local CameraEventHandlers = {} + +function CameraEventHandlers.zone_triggered_handler(driver, device, ib, response) + local triggered_zones = device:get_field(camera_fields.TRIGGERED_ZONES) or {} + if not switch_utils.tbl_contains(triggered_zones, ib.data.elements.zone.value) then + table.insert(triggered_zones, {zoneId = ib.data.elements.zone.value}) + device:set_field(camera_fields.TRIGGERED_ZONES, triggered_zones) + device:emit_event_for_endpoint(ib, capabilities.zoneManagement.triggeredZones(triggered_zones)) + end +end + +function CameraEventHandlers.zone_stopped_handler(driver, device, ib, response) + local triggered_zones = device:get_field(camera_fields.TRIGGERED_ZONES) or {} + for i, v in pairs(triggered_zones) do + if v.zoneId == ib.data.elements.zone.value then + table.remove(triggered_zones, i) + device:set_field(camera_fields.TRIGGERED_ZONES, triggered_zones) + device:emit_event_for_endpoint(ib, capabilities.zoneManagement.triggeredZones(triggered_zones)) + end + end +end + +return CameraEventHandlers diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/device_configuration.lua new file mode 100644 index 0000000000..c84337736d --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/device_configuration.lua @@ -0,0 +1,283 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local button_cfg = require("switch_utils.device_configuration").ButtonCfg +local camera_fields = require "sub_drivers.camera.camera_utils.fields" +local camera_utils = require "sub_drivers.camera.camera_utils.utils" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local device_cfg = require "switch_utils.device_configuration" +local fields = require "switch_utils.fields" +local switch_utils = require "switch_utils.utils" + +local CameraDeviceConfiguration = {} + +function CameraDeviceConfiguration.create_child_devices(driver, device) + local num_floodlight_eps = 0 + local parent_child_device = false + for _, ep in ipairs(device.endpoints or {}) do + if device:supports_server_cluster(clusters.OnOff.ID, ep.endpoint_id) then + local child_profile = device_cfg.SwitchCfg.assign_profile_for_onoff_ep(device, ep.endpoint_id) + if child_profile then + num_floodlight_eps = num_floodlight_eps + 1 + local name = string.format("%s %d", "Floodlight", num_floodlight_eps) + driver:try_create_device( + { + type = "EDGE_CHILD", + label = name, + profile = child_profile, + parent_device_id = device.id, + parent_assigned_child_key = string.format("%d", ep.endpoint_id), + vendor_provided_label = name + } + ) + parent_child_device = true + end + end + end + if parent_child_device then + device:set_field(fields.IS_PARENT_CHILD_DEVICE, true, {persist = true}) + device:set_find_child(switch_utils.find_child) + end +end + +function CameraDeviceConfiguration.match_profile(device, status_light_enabled_present, status_light_brightness_present) + local optional_supported_component_capabilities = {} + local main_component_capabilities = {} + local status_led_component_capabilities = {} + local speaker_component_capabilities = {} + local microphone_component_capabilities = {} + local doorbell_component_capabilities = {} + + local function has_server_cluster_type(cluster) + return cluster.cluster_type == "SERVER" or cluster.cluster_type == "BOTH" + end + + local camera_endpoints = switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.CAMERA) + if #camera_endpoints > 0 then + local camera_ep = switch_utils.get_endpoint_info(device, camera_endpoints[1]) + for _, ep_cluster in pairs(camera_ep.clusters or {}) do + if ep_cluster.cluster_id == clusters.CameraAvStreamManagement.ID and has_server_cluster_type(ep_cluster) then + local clus_has_feature = function(feature_bitmap) + return clusters.CameraAvStreamManagement.are_features_supported(feature_bitmap, ep_cluster.feature_map) + end + if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.VIDEO) then + if switch_utils.find_cluster_on_ep(camera_ep, clusters.PushAvStreamTransport.ID, "SERVER") then + table.insert(main_component_capabilities, capabilities.videoCapture2.ID) + end + table.insert(main_component_capabilities, capabilities.cameraViewportSettings.ID) + end + if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.LOCAL_STORAGE) then + table.insert(main_component_capabilities, capabilities.localMediaStorage.ID) + end + if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.AUDIO) then + table.insert(main_component_capabilities, capabilities.audioRecording.ID) + table.insert(microphone_component_capabilities, capabilities.audioMute.ID) + table.insert(microphone_component_capabilities, capabilities.audioVolume.ID) + end + if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.SNAPSHOT) then + table.insert(main_component_capabilities, capabilities.imageCapture.ID) + end + if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.PRIVACY) then + table.insert(main_component_capabilities, capabilities.cameraPrivacyMode.ID) + end + if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.SPEAKER) then + table.insert(speaker_component_capabilities, capabilities.audioMute.ID) + table.insert(speaker_component_capabilities, capabilities.audioVolume.ID) + end + if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.IMAGE_CONTROL) then + table.insert(main_component_capabilities, capabilities.imageControl.ID) + end + if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.HIGH_DYNAMIC_RANGE) then + table.insert(main_component_capabilities, capabilities.hdr.ID) + end + if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.NIGHT_VISION) then + table.insert(main_component_capabilities, capabilities.nightVision.ID) + end + elseif ep_cluster.cluster_id == clusters.CameraAvSettingsUserLevelManagement.ID and has_server_cluster_type(ep_cluster) then + local clus_has_feature = function(feature_bitmap) + return clusters.CameraAvSettingsUserLevelManagement.are_features_supported(feature_bitmap, ep_cluster.feature_map) + end + if clus_has_feature(clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PAN) or + clus_has_feature(clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_TILT) or + clus_has_feature(clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_ZOOM) then + table.insert(main_component_capabilities, capabilities.mechanicalPanTiltZoom.ID) + end + table.insert(main_component_capabilities, capabilities.videoStreamSettings.ID) + elseif ep_cluster.cluster_id == clusters.ZoneManagement.ID and has_server_cluster_type(ep_cluster) then + table.insert(main_component_capabilities, capabilities.zoneManagement.ID) + elseif ep_cluster.cluster_id == clusters.OccupancySensing.ID and has_server_cluster_type(ep_cluster) then + table.insert(main_component_capabilities, capabilities.motionSensor.ID) + elseif ep_cluster.cluster_id == clusters.WebRTCTransportProvider.ID and has_server_cluster_type(ep_cluster) and + #device:get_endpoints(clusters.WebRTCTransportRequestor.ID, {cluster_type = "CLIENT"}) > 0 then + table.insert(main_component_capabilities, capabilities.webrtc.ID) + end + end + end + local chime_endpoints = switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.CHIME) + if #chime_endpoints > 0 then + table.insert(main_component_capabilities, capabilities.sounds.ID) + end + local doorbell_endpoints = switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.DOORBELL) + if #doorbell_endpoints > 0 then + table.insert(doorbell_component_capabilities, capabilities.button.ID) + end + if status_light_enabled_present then + table.insert(status_led_component_capabilities, capabilities.switch.ID) + end + if status_light_brightness_present then + table.insert(status_led_component_capabilities, capabilities.mode.ID) + end + + table.insert(optional_supported_component_capabilities, {camera_fields.profile_components.main, main_component_capabilities}) + if #status_led_component_capabilities > 0 then + table.insert(optional_supported_component_capabilities, {camera_fields.profile_components.statusLed, status_led_component_capabilities}) + end + if #speaker_component_capabilities > 0 then + table.insert(optional_supported_component_capabilities, {camera_fields.profile_components.speaker, speaker_component_capabilities}) + end + if #microphone_component_capabilities > 0 then + table.insert(optional_supported_component_capabilities, {camera_fields.profile_components.microphone, microphone_component_capabilities}) + end + if #doorbell_component_capabilities > 0 then + table.insert(optional_supported_component_capabilities, {camera_fields.profile_components.doorbell, doorbell_component_capabilities}) + end + + if camera_utils.optional_capabilities_list_changed(optional_supported_component_capabilities, device.profile.components) then + device:try_update_metadata({profile = "camera", optional_component_capabilities = optional_supported_component_capabilities}) + if #doorbell_endpoints > 0 then + CameraDeviceConfiguration.update_doorbell_component_map(device, doorbell_endpoints[1]) + button_cfg.configure_buttons(device, device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH})) + end + end +end + +local function init_webrtc(device) + if device:supports_capability(capabilities.webrtc) then + -- TODO: Check for individual audio/video and talkback features + local transport_provider_ep_ids = device:get_endpoints(clusters.WebRTCTransportProvider.ID) + device:emit_event_for_endpoint(transport_provider_ep_ids[1], capabilities.webrtc.supportedFeatures({ + value = { + bundle = true, + order = "audio/video", + audio = "sendrecv", + video = "recvonly", + turnSource = "player", + supportTrickleICE = true + } + })) + end +end + +local function init_ptz(device) + if device:supports_capability(capabilities.mechanicalPanTiltZoom) then + local supported_attributes = {} + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MPAN) then + table.insert(supported_attributes, "pan") + table.insert(supported_attributes, "panRange") + end + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MTILT) then + table.insert(supported_attributes, "tilt") + table.insert(supported_attributes, "tiltRange") + end + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MZOOM) then + table.insert(supported_attributes, "zoom") + table.insert(supported_attributes, "zoomRange") + end + if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MPRESETS) then + table.insert(supported_attributes, "presets") + table.insert(supported_attributes, "maxPresets") + end + local av_settings_ep_ids = device:get_endpoints(clusters.CameraAvSettingsUserLevelManagement.ID) + device:emit_event_for_endpoint(av_settings_ep_ids[1], capabilities.mechanicalPanTiltZoom.supportedAttributes(supported_attributes)) + end +end + +local function init_zone_management(device) + if device:supports_capability(capabilities.zoneManagement) then + local supported_features = {} + table.insert(supported_features, "triggerAugmentation") + if camera_utils.feature_supported(device, clusters.ZoneManagement.ID, clusters.ZoneManagement.types.Feature.PER_ZONE_SENSITIVITY) then + table.insert(supported_features, "perZoneSensitivity") + end + local zone_management_ep_ids = device:get_endpoints(clusters.ZoneManagement.ID) + device:emit_event_for_endpoint(zone_management_ep_ids[1], capabilities.zoneManagement.supportedFeatures(supported_features)) + end +end + +local function init_local_media_storage(device) + if device:supports_capability(capabilities.localMediaStorage) then + local supported_attributes = {} + if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.VIDEO) then + table.insert(supported_attributes, "localVideoRecording") + end + if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.SNAPSHOT) then + table.insert(supported_attributes, "localSnapshotRecording") + end + local av_stream_management_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.localMediaStorage.supportedAttributes(supported_attributes)) + end +end + +local function init_audio_recording(device) + if device:supports_capability(capabilities.audioRecording) then + local audio_enabled_state = device:get_latest_state( + camera_fields.profile_components.main, capabilities.audioRecording.ID, capabilities.audioRecording.audioRecording.NAME + ) + if audio_enabled_state == nil then + -- Initialize with enabled default if state is unset + local av_stream_management_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.audioRecording.audioRecording("enabled")) + end + end +end + +local function init_video_stream_settings(device) + if device:supports_capability(capabilities.videoStreamSettings) then + local supported_features = {} + if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.VIDEO) then + table.insert(supported_features, "liveStreaming") + table.insert(supported_features, "clipRecording") + table.insert(supported_features, "perStreamViewports") + end + if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.WATERMARK) then + table.insert(supported_features, "watermark") + end + if camera_utils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.ON_SCREEN_DISPLAY) then + table.insert(supported_features, "onScreenDisplay") + end + local av_stream_management_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.videoStreamSettings.supportedFeatures(supported_features)) + end +end + +local function init_camera_privacy_mode(device) + if device:supports_capability(capabilities.cameraPrivacyMode) then + local supported_attributes, supported_commands = {}, {} + table.insert(supported_attributes, "softRecordingPrivacyMode") + table.insert(supported_attributes, "softLivestreamPrivacyMode") + table.insert(supported_commands, "setSoftRecordingPrivacyMode") + table.insert(supported_commands, "setSoftLivestreamPrivacyMode") + local av_stream_management_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.cameraPrivacyMode.supportedAttributes(supported_attributes)) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.cameraPrivacyMode.supportedCommands(supported_commands)) + end +end + +function CameraDeviceConfiguration.initialize_camera_capabilities(device) + init_webrtc(device) + init_ptz(device) + init_zone_management(device) + init_local_media_storage(device) + init_audio_recording(device) + init_video_stream_settings(device) + init_camera_privacy_mode(device) +end + +function CameraDeviceConfiguration.update_doorbell_component_map(device, ep) + local component_map = device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) or {} + component_map.doorbell = ep + device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, component_map, {persist = true}) +end + +return CameraDeviceConfiguration diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/fields.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/fields.lua new file mode 100644 index 0000000000..677e2d5dd6 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/fields.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local clusters = require "st.matter.clusters" + +local CameraFields = {} + +CameraFields.MAX_ENCODED_PIXEL_RATE = "__max_encoded_pixel_rate" +CameraFields.MAX_FRAMES_PER_SECOND = "__max_frames_per_second" +CameraFields.MAX_VOLUME_LEVEL = "__max_volume_level" +CameraFields.MIN_VOLUME_LEVEL = "__min_volume_level" +CameraFields.SUPPORTED_RESOLUTIONS = "__supported_resolutions" +CameraFields.TRIGGERED_ZONES = "__triggered_zones" +CameraFields.VIEWPORT = "__viewport" + +CameraFields.PAN_IDX = "PAN" +CameraFields.TILT_IDX = "TILT" +CameraFields.ZOOM_IDX = "ZOOM" + +CameraFields.pt_range_fields = { + [CameraFields.PAN_IDX] = { max = "__MAX_PAN" , min = "__MIN_PAN" }, + [CameraFields.TILT_IDX] = { max = "__MAX_TILT" , min = "__MIN_TILT" } +} + +CameraFields.profile_components = { + main = "main", + statusLed = "statusLed", + speaker = "speaker", + microphone = "microphone", + doorbell = "doorbell" +} + +CameraFields.tri_state_map = { + [clusters.CameraAvStreamManagement.types.TriStateAutoEnum.OFF] = "off", + [clusters.CameraAvStreamManagement.types.TriStateAutoEnum.ON] = "on", + [clusters.CameraAvStreamManagement.types.TriStateAutoEnum.AUTO] = "auto" +} + +CameraFields.ABS_PAN_MAX = 180 +CameraFields.ABS_PAN_MIN = -180 +CameraFields.ABS_TILT_MAX = 180 +CameraFields.ABS_TILT_MIN = -180 +CameraFields.ABS_ZOOM_MAX = 100 +CameraFields.ABS_ZOOM_MIN = 1 +CameraFields.ABS_VOL_MAX = 254.0 +CameraFields.ABS_VOL_MIN = 0.0 + +return CameraFields diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/utils.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/utils.lua new file mode 100644 index 0000000000..a93e757c16 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/utils.lua @@ -0,0 +1,306 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local camera_fields = require "sub_drivers.camera.camera_utils.fields" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local fields = require "switch_utils.fields" +local switch_utils = require "switch_utils.utils" + +local CameraUtils = {} + +function CameraUtils.component_to_endpoint(device, component) + local camera_eps = device:get_endpoints(clusters.CameraAvStreamManagement.ID) + table.sort(camera_eps) + for _, ep in ipairs(camera_eps or {}) do + if ep ~= 0 then -- 0 is the matter RootNode endpoint + return ep + end + end + return nil +end + +function CameraUtils.update_camera_component_map(device) + local camera_av_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) + if #camera_av_ep_ids > 0 then + -- An assumption here: there is only 1 CameraAvStreamManagement cluster on the device (which is all our profile supports) + local component_map = {} + if CameraUtils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.AUDIO) then + component_map.microphone = { + endpoint_id = camera_av_ep_ids[1], + cluster_id = clusters.CameraAvStreamManagement.ID, + attribute_ids = { + clusters.CameraAvStreamManagement.attributes.MicrophoneMuted.ID, + clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel.ID, + clusters.CameraAvStreamManagement.attributes.MicrophoneMaxLevel.ID, + clusters.CameraAvStreamManagement.attributes.MicrophoneMinLevel.ID, + }, + } + end + if CameraUtils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.VIDEO) then + component_map.speaker = { + endpoint_id = camera_av_ep_ids[1], + cluster_id = clusters.CameraAvStreamManagement.ID, + attribute_ids = { + clusters.CameraAvStreamManagement.attributes.SpeakerMuted.ID, + clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel.ID, + clusters.CameraAvStreamManagement.attributes.SpeakerMaxLevel.ID, + clusters.CameraAvStreamManagement.attributes.SpeakerMinLevel.ID, + }, + } + end + device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, component_map, {persist = true}) + end +end + +function CameraUtils.get_ptz_map(device) + local mechanicalPanTiltZoom = capabilities.mechanicalPanTiltZoom + local ptz_map = { + [camera_fields.PAN_IDX] = { + current = device:get_latest_state("main", mechanicalPanTiltZoom.ID, mechanicalPanTiltZoom.pan.NAME), + range = device:get_latest_state("main", mechanicalPanTiltZoom.ID, mechanicalPanTiltZoom.panRange.NAME) or + { minimum = camera_fields.ABS_PAN_MIN, maximum = camera_fields.ABS_PAN_MAX }, + attribute = mechanicalPanTiltZoom.pan + }, + [camera_fields.TILT_IDX] = { + current = device:get_latest_state("main", mechanicalPanTiltZoom.ID, mechanicalPanTiltZoom.tilt.NAME), + range = device:get_latest_state("main", mechanicalPanTiltZoom.ID, mechanicalPanTiltZoom.tiltRange.NAME) or + { minimum = camera_fields.ABS_TILT_MIN, maximum = camera_fields.ABS_TILT_MAX }, + attribute = mechanicalPanTiltZoom.tilt + }, + [camera_fields.ZOOM_IDX] = { + current = device:get_latest_state("main", mechanicalPanTiltZoom.ID, mechanicalPanTiltZoom.zoom.NAME), + range = device:get_latest_state("main", mechanicalPanTiltZoom.ID, mechanicalPanTiltZoom.zoomRange.NAME) or + { minimum = camera_fields.ABS_ZOOM_MIN, maximum = camera_fields.ABS_ZOOM_MAX }, + attribute = mechanicalPanTiltZoom.zoom + } + } + return ptz_map +end + +function CameraUtils.feature_supported(device, cluster_id, feature_flag) + return #device:get_endpoints(cluster_id, { feature_bitmap = feature_flag }) > 0 +end + +function CameraUtils.update_supported_attributes(device, ib, capability, attribute) + local attribute_set = device:get_latest_state( + camera_fields.profile_components.main, capability.ID, capability.supportedAttributes.NAME + ) or {} + if not switch_utils.tbl_contains(attribute_set, attribute) then + local updated_attribute_set = {} + for _, v in ipairs(attribute_set) do + table.insert(updated_attribute_set, v) + end + table.insert(updated_attribute_set, attribute) + device:emit_event_for_endpoint(ib, capability.supportedAttributes(updated_attribute_set)) + end +end + +function CameraUtils.compute_fps(max_encoded_pixel_rate, width, height, max_fps) + local fps_step = 15.0 + local fps = math.min(max_encoded_pixel_rate / (width * height), max_fps) + return math.tointeger(math.floor(fps / fps_step) * fps_step) +end + +function CameraUtils.profile_changed(synced_components, prev_components) + if #synced_components ~= #prev_components then + return true + end + for _, component in pairs(synced_components or {}) do + if (prev_components[component.id] == nil) or + (#component.capabilities ~= #prev_components[component.id].capabilities) then + return true + end + for _, capability in pairs(component.capabilities or {}) do + if prev_components[component.id][capability.id] == nil then + return true + end + end + end + return false +end + +function CameraUtils.optional_capabilities_list_changed(new_component_capability_list, previous_component_capability_list) + local previous_capability_map = {} + local component_sizes = {} + + local previous_component_count = 0 + for component_name, component in pairs(previous_component_capability_list or {}) do + previous_capability_map[component_name] = {} + component_sizes[component_name] = 0 + for _, capability in pairs(component.capabilities or {}) do + if capability.id ~= "firmwareUpdate" and capability.id ~= "refresh" then + previous_capability_map[component_name][capability.id] = true + component_sizes[component_name] = component_sizes[component_name] + 1 + end + end + previous_component_count = previous_component_count + 1 + end + + local number_of_components_counted = 0 + for _, new_component_capabilities in pairs(new_component_capability_list or {}) do + local component_name = new_component_capabilities[1] + local capability_list = new_component_capabilities[2] + + number_of_components_counted = number_of_components_counted + 1 + + if previous_capability_map[component_name] == nil then + return true + end + + for _, capability in ipairs(capability_list) do + if previous_capability_map[component_name][capability] == nil then + return true + end + end + + if #capability_list ~= component_sizes[component_name] then + return true + end + end + + if number_of_components_counted ~= previous_component_count then + return true + end + + return false +end + +function CameraUtils.subscribe(device) + local camera_subscribed_attributes = { + [capabilities.hdr.ID] = { + clusters.CameraAvStreamManagement.attributes.HDRModeEnabled, + clusters.CameraAvStreamManagement.attributes.ImageRotation + }, + [capabilities.nightVision.ID] = { + clusters.CameraAvStreamManagement.attributes.NightVision, + clusters.CameraAvStreamManagement.attributes.NightVisionIllum + }, + [capabilities.imageControl.ID] = { + clusters.CameraAvStreamManagement.attributes.ImageFlipHorizontal, + clusters.CameraAvStreamManagement.attributes.ImageFlipVertical + }, + [capabilities.cameraPrivacyMode.ID] = { + clusters.CameraAvStreamManagement.attributes.SoftRecordingPrivacyModeEnabled, + clusters.CameraAvStreamManagement.attributes.SoftLivestreamPrivacyModeEnabled, + clusters.CameraAvStreamManagement.attributes.HardPrivacyModeOn + }, + [capabilities.webrtc.ID] = { + clusters.CameraAvStreamManagement.attributes.TwoWayTalkSupport + }, + [capabilities.mechanicalPanTiltZoom.ID] = { + clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPosition, + clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPresets, + clusters.CameraAvSettingsUserLevelManagement.attributes.MaxPresets, + clusters.CameraAvSettingsUserLevelManagement.attributes.ZoomMax, + clusters.CameraAvSettingsUserLevelManagement.attributes.PanMax, + clusters.CameraAvSettingsUserLevelManagement.attributes.PanMin, + clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMax, + clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMin + }, + [capabilities.audioMute.ID] = { + clusters.CameraAvStreamManagement.attributes.SpeakerMuted, + clusters.CameraAvStreamManagement.attributes.MicrophoneMuted + }, + [capabilities.audioVolume.ID] = { + clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel, + clusters.CameraAvStreamManagement.attributes.SpeakerMaxLevel, + clusters.CameraAvStreamManagement.attributes.SpeakerMinLevel, + clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel, + clusters.CameraAvStreamManagement.attributes.MicrophoneMaxLevel, + clusters.CameraAvStreamManagement.attributes.MicrophoneMinLevel + }, + [capabilities.mode.ID] = { + clusters.CameraAvStreamManagement.attributes.StatusLightBrightness + }, + [capabilities.switch.ID] = { + clusters.CameraAvStreamManagement.attributes.StatusLightEnabled, + clusters.OnOff.attributes.OnOff + }, + [capabilities.videoStreamSettings.ID] = { + clusters.CameraAvStreamManagement.attributes.RateDistortionTradeOffPoints, + clusters.CameraAvStreamManagement.attributes.MaxEncodedPixelRate, + clusters.CameraAvStreamManagement.attributes.VideoSensorParams, + clusters.CameraAvStreamManagement.attributes.AllocatedVideoStreams + }, + [capabilities.zoneManagement.ID] = { + clusters.ZoneManagement.attributes.MaxZones, + clusters.ZoneManagement.attributes.Zones, + clusters.ZoneManagement.attributes.Triggers, + clusters.ZoneManagement.attributes.SensitivityMax, + clusters.ZoneManagement.attributes.Sensitivity + }, + [capabilities.sounds.ID] = { + clusters.Chime.attributes.InstalledChimeSounds, + clusters.Chime.attributes.SelectedChime + }, + [capabilities.localMediaStorage.ID] = { + clusters.CameraAvStreamManagement.attributes.LocalSnapshotRecordingEnabled, + clusters.CameraAvStreamManagement.attributes.LocalVideoRecordingEnabled + }, + [capabilities.cameraViewportSettings.ID] = { + clusters.CameraAvStreamManagement.attributes.MinViewportResolution, + clusters.CameraAvStreamManagement.attributes.VideoSensorParams, + clusters.CameraAvStreamManagement.attributes.Viewport + }, + [capabilities.motionSensor.ID] = { + clusters.OccupancySensing.attributes.Occupancy + }, + [capabilities.switchLevel.ID] = { + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel, + }, + [capabilities.colorControl.ID] = { + clusters.ColorControl.attributes.ColorMode, + clusters.ColorControl.attributes.CurrentHue, + clusters.ColorControl.attributes.CurrentSaturation, + clusters.ColorControl.attributes.CurrentX, + clusters.ColorControl.attributes.CurrentY, + }, + [capabilities.colorTemperature.ID] = { + clusters.ColorControl.attributes.ColorTemperatureMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, + }, + } + + local camera_subscribed_events = { + [capabilities.zoneManagement.ID] = { + clusters.ZoneManagement.events.ZoneTriggered, + clusters.ZoneManagement.events.ZoneStopped + }, + [capabilities.button.ID] = { + clusters.Switch.events.InitialPress, + clusters.Switch.events.LongPress, + clusters.Switch.events.ShortRelease, + clusters.Switch.events.MultiPressComplete + } + } + + local im = require "st.matter.interaction_model" + + local subscribe_request = im.InteractionRequest(im.InteractionRequest.RequestType.SUBSCRIBE, {}) + local devices_seen, capabilities_seen, attributes_seen, events_seen = {}, {}, {}, {} + + if #device:get_endpoints(clusters.CameraAvStreamManagement.ID) > 0 then + local ib = im.InteractionInfoBlock(nil, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.attributes.AttributeList.ID) + subscribe_request:with_info_block(ib) + end + + for _, endpoint_info in ipairs(device.endpoints) do + local checked_device = switch_utils.find_child(device, endpoint_info.endpoint_id) or device + if not devices_seen[checked_device.id] then + switch_utils.populate_subscribe_request_for_device(checked_device, subscribe_request, capabilities_seen, attributes_seen, events_seen, + camera_subscribed_attributes, camera_subscribed_events + ) + devices_seen[checked_device.id] = true -- only loop through any device once + end + end + + if #subscribe_request.info_blocks > 0 then + device:send(subscribe_request) + end +end + +return CameraUtils diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/can_handle.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/can_handle.lua new file mode 100644 index 0000000000..25a441d641 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local device_lib = require "st.device" + local fields = require "switch_utils.fields" + local switch_utils = require "switch_utils.utils" + if device.network_type == device_lib.NETWORK_TYPE_MATTER then + local version = require "version" + if version.rpc >= 10 and version.api >= 16 and + #switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.CAMERA) > 0 then + return true, require("sub_drivers.camera") + end + end + return false +end diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua new file mode 100644 index 0000000000..f13589ff41 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -0,0 +1,207 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +------------------------------------------------------------------------------------- +-- Matter Camera Sub Driver +------------------------------------------------------------------------------------- + +local attribute_handlers = require "sub_drivers.camera.camera_handlers.attribute_handlers" +local button_cfg = require("switch_utils.device_configuration").ButtonCfg +local camera_cfg = require "sub_drivers.camera.camera_utils.device_configuration" +local camera_fields = require "sub_drivers.camera.camera_utils.fields" +local camera_utils = require "sub_drivers.camera.camera_utils.utils" +local capabilities = require "st.capabilities" +local capability_handlers = require "sub_drivers.camera.camera_handlers.capability_handlers" +local clusters = require "st.matter.clusters" +local event_handlers = require "sub_drivers.camera.camera_handlers.event_handlers" +local fields = require "switch_utils.fields" +local switch_utils = require "switch_utils.utils" + +local CameraLifecycleHandlers = {} + +function CameraLifecycleHandlers.device_init(driver, device) + device:set_component_to_endpoint_fn(camera_utils.component_to_endpoint) + device:set_endpoint_to_component_fn(switch_utils.endpoint_to_component) + device:extend_device("emit_event_for_endpoint", switch_utils.emit_event_for_endpoint) + if device:get_field(fields.IS_PARENT_CHILD_DEVICE) then + device:set_find_child(switch_utils.find_child) + end + device:extend_device("subscribe", camera_utils.subscribe) + device:subscribe() +end + +function CameraLifecycleHandlers.do_configure(driver, device) + camera_utils.update_camera_component_map(device) + if #device:get_endpoints(clusters.CameraAvStreamManagement.ID) == 0 then + camera_cfg.match_profile(device, false, false) + end + camera_cfg.create_child_devices(driver, device) + camera_cfg.initialize_camera_capabilities(device) +end + +function CameraLifecycleHandlers.driver_switched(driver, device) + camera_utils.update_camera_component_map(device) + if #device:get_endpoints(clusters.CameraAvStreamManagement.ID) == 0 then + camera_cfg.match_profile(device, false, false) + end +end + +function CameraLifecycleHandlers.info_changed(driver, device, event, args) + if camera_utils.profile_changed(device.profile.components, args.old_st_store.profile.components) then + camera_cfg.initialize_camera_capabilities(device) + device:subscribe() + if #switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.DOORBELL) > 0 then + button_cfg.configure_buttons(device, device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH})) + end + end +end + +function CameraLifecycleHandlers.added() end + +local camera_handler = { + NAME = "Camera Handler", + lifecycle_handlers = { + init = CameraLifecycleHandlers.device_init, + infoChanged = CameraLifecycleHandlers.info_changed, + doConfigure = CameraLifecycleHandlers.do_configure, + driverSwitched = CameraLifecycleHandlers.driver_switched, + added = CameraLifecycleHandlers.added + }, + matter_handlers = { + attr = { + [clusters.CameraAvStreamManagement.ID] = { + [clusters.CameraAvStreamManagement.attributes.HDRModeEnabled.ID] = attribute_handlers.enabled_state_factory(capabilities.hdr.hdr), + [clusters.CameraAvStreamManagement.attributes.NightVision.ID] = attribute_handlers.night_vision_factory(capabilities.nightVision.nightVision), + [clusters.CameraAvStreamManagement.attributes.NightVisionIllum.ID] = attribute_handlers.night_vision_factory(capabilities.nightVision.illumination), + [clusters.CameraAvStreamManagement.attributes.ImageFlipHorizontal.ID] = attribute_handlers.enabled_state_factory(capabilities.imageControl.imageFlipHorizontal), + [clusters.CameraAvStreamManagement.attributes.ImageFlipVertical.ID] = attribute_handlers.enabled_state_factory(capabilities.imageControl.imageFlipVertical), + [clusters.CameraAvStreamManagement.attributes.ImageRotation.ID] = attribute_handlers.image_rotation_handler, + [clusters.CameraAvStreamManagement.attributes.SoftRecordingPrivacyModeEnabled.ID] = attribute_handlers.enabled_state_factory(capabilities.cameraPrivacyMode.softRecordingPrivacyMode), + [clusters.CameraAvStreamManagement.attributes.SoftLivestreamPrivacyModeEnabled.ID] = attribute_handlers.enabled_state_factory(capabilities.cameraPrivacyMode.softLivestreamPrivacyMode), + [clusters.CameraAvStreamManagement.attributes.HardPrivacyModeOn.ID] = attribute_handlers.enabled_state_factory(capabilities.cameraPrivacyMode.hardPrivacyMode), + [clusters.CameraAvStreamManagement.attributes.TwoWayTalkSupport.ID] = attribute_handlers.two_way_talk_support_handler, + [clusters.CameraAvStreamManagement.attributes.SpeakerMuted.ID] = attribute_handlers.muted_handler, + [clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel.ID] = attribute_handlers.volume_level_handler, + [clusters.CameraAvStreamManagement.attributes.SpeakerMaxLevel.ID] = attribute_handlers.max_volume_level_handler, + [clusters.CameraAvStreamManagement.attributes.SpeakerMinLevel.ID] = attribute_handlers.min_volume_level_handler, + [clusters.CameraAvStreamManagement.attributes.MicrophoneMuted.ID] = attribute_handlers.muted_handler, + [clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel.ID] = attribute_handlers.volume_level_handler, + [clusters.CameraAvStreamManagement.attributes.MicrophoneMaxLevel.ID] = attribute_handlers.max_volume_level_handler, + [clusters.CameraAvStreamManagement.attributes.MicrophoneMinLevel.ID] = attribute_handlers.min_volume_level_handler, + [clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID] = attribute_handlers.status_light_enabled_handler, + [clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID] = attribute_handlers.status_light_brightness_handler, + [clusters.CameraAvStreamManagement.attributes.RateDistortionTradeOffPoints.ID] = attribute_handlers.rate_distortion_trade_off_points_handler, + [clusters.CameraAvStreamManagement.attributes.MaxEncodedPixelRate.ID] = attribute_handlers.max_encoded_pixel_rate_handler, + [clusters.CameraAvStreamManagement.attributes.VideoSensorParams.ID] = attribute_handlers.video_sensor_parameters_handler, + [clusters.CameraAvStreamManagement.attributes.MinViewportResolution.ID] = attribute_handlers.min_viewport_handler, + [clusters.CameraAvStreamManagement.attributes.AllocatedVideoStreams.ID] = attribute_handlers.allocated_video_streams_handler, + [clusters.CameraAvStreamManagement.attributes.Viewport.ID] = attribute_handlers.viewport_handler, + [clusters.CameraAvStreamManagement.attributes.LocalSnapshotRecordingEnabled.ID] = attribute_handlers.enabled_state_factory(capabilities.localMediaStorage.localSnapshotRecording), + [clusters.CameraAvStreamManagement.attributes.LocalVideoRecordingEnabled.ID] = attribute_handlers.enabled_state_factory(capabilities.localMediaStorage.localVideoRecording), + [clusters.CameraAvStreamManagement.attributes.AttributeList.ID] = attribute_handlers.camera_av_stream_management_attribute_list_handler + }, + [clusters.CameraAvSettingsUserLevelManagement.ID] = { + [clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPosition.ID] = attribute_handlers.ptz_position_handler, + [clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPresets.ID] = attribute_handlers.ptz_presets_handler, + [clusters.CameraAvSettingsUserLevelManagement.attributes.MaxPresets.ID] = attribute_handlers.max_presets_handler, + [clusters.CameraAvSettingsUserLevelManagement.attributes.ZoomMax.ID] = attribute_handlers.zoom_max_handler, + [clusters.CameraAvSettingsUserLevelManagement.attributes.PanMax.ID] = attribute_handlers.pt_range_handler_factory(capabilities.mechanicalPanTiltZoom.panRange, camera_fields.pt_range_fields[camera_fields.PAN_IDX].max), + [clusters.CameraAvSettingsUserLevelManagement.attributes.PanMin.ID] = attribute_handlers.pt_range_handler_factory(capabilities.mechanicalPanTiltZoom.panRange, camera_fields.pt_range_fields[camera_fields.PAN_IDX].min), + [clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMax.ID] = attribute_handlers.pt_range_handler_factory(capabilities.mechanicalPanTiltZoom.tiltRange, camera_fields.pt_range_fields[camera_fields.TILT_IDX].max), + [clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMin.ID] = attribute_handlers.pt_range_handler_factory(capabilities.mechanicalPanTiltZoom.tiltRange, camera_fields.pt_range_fields[camera_fields.TILT_IDX].min) + }, + [clusters.ZoneManagement.ID] = { + [clusters.ZoneManagement.attributes.MaxZones.ID] = attribute_handlers.max_zones_handler, + [clusters.ZoneManagement.attributes.Zones.ID] = attribute_handlers.zones_handler, + [clusters.ZoneManagement.attributes.Triggers.ID] = attribute_handlers.triggers_handler, + [clusters.ZoneManagement.attributes.SensitivityMax.ID] = attribute_handlers.sensitivity_max_handler, + [clusters.ZoneManagement.attributes.Sensitivity.ID] = attribute_handlers.sensitivity_handler, + }, + [clusters.Chime.ID] = { + [clusters.Chime.attributes.InstalledChimeSounds.ID] = attribute_handlers.installed_chime_sounds_handler, + [clusters.Chime.attributes.SelectedChime.ID] = attribute_handlers.selected_chime_handler + } + }, + event = { + [clusters.ZoneManagement.ID] = { + [clusters.ZoneManagement.events.ZoneTriggered.ID] = event_handlers.zone_triggered_handler, + [clusters.ZoneManagement.events.ZoneStopped.ID] = event_handlers.zone_stopped_handler + } + } + }, + capability_handlers = { + [capabilities.hdr.ID] = { + [capabilities.hdr.commands.setHdr.NAME] = capability_handlers.set_enabled_factory(clusters.CameraAvStreamManagement.attributes.HDRModeEnabled) + }, + [capabilities.nightVision.ID] = { + [capabilities.nightVision.commands.setNightVision.NAME] = capability_handlers.set_night_vision_factory(clusters.CameraAvStreamManagement.attributes.NightVision), + [capabilities.nightVision.commands.setIllumination.NAME] = capability_handlers.set_night_vision_factory(clusters.CameraAvStreamManagement.attributes.NightVisionIllum) + }, + [capabilities.imageControl.ID] = { + [capabilities.imageControl.commands.setImageFlipHorizontal.NAME] = capability_handlers.set_enabled_factory(clusters.CameraAvStreamManagement.attributes.ImageFlipHorizontal), + [capabilities.imageControl.commands.setImageFlipVertical.NAME] = capability_handlers.set_enabled_factory(clusters.CameraAvStreamManagement.attributes.ImageFlipVertical), + [capabilities.imageControl.commands.setImageRotation.NAME] = capability_handlers.handle_set_image_rotation + }, + [capabilities.cameraPrivacyMode.ID] = { + [capabilities.cameraPrivacyMode.commands.setSoftLivestreamPrivacyMode.NAME] = capability_handlers.set_enabled_factory(clusters.CameraAvStreamManagement.attributes.SoftLivestreamPrivacyModeEnabled), + [capabilities.cameraPrivacyMode.commands.setSoftRecordingPrivacyMode.NAME] = capability_handlers.set_enabled_factory(clusters.CameraAvStreamManagement.attributes.SoftRecordingPrivacyModeEnabled) + }, + [capabilities.audioMute.ID] = { + [capabilities.audioMute.commands.setMute.NAME] = capability_handlers.handle_mute_commands_factory(capabilities.audioMute.commands.setMute.NAME), + [capabilities.audioMute.commands.mute.NAME] = capability_handlers.handle_mute_commands_factory(capabilities.audioMute.commands.mute.NAME), + [capabilities.audioMute.commands.unmute.NAME] = capability_handlers.handle_mute_commands_factory(capabilities.audioMute.commands.unmute.NAME) + }, + [capabilities.audioVolume.ID] = { + [capabilities.audioVolume.commands.setVolume.NAME] = capability_handlers.handle_set_volume, + [capabilities.audioVolume.commands.volumeUp.NAME] = capability_handlers.handle_volume_up, + [capabilities.audioVolume.commands.volumeDown.NAME] = capability_handlers.handle_volume_down + }, + [capabilities.mode.ID] = { + [capabilities.mode.commands.setMode.NAME] = capability_handlers.handle_set_status_light_mode + }, + [capabilities.switch.ID] = { + [capabilities.switch.commands.on.NAME] = capability_handlers.handle_status_led_on, + [capabilities.switch.commands.off.NAME] = capability_handlers.handle_status_led_off + }, + [capabilities.audioRecording.ID] = { + [capabilities.audioRecording.commands.setAudioRecording.NAME] = capability_handlers.handle_audio_recording + }, + [capabilities.mechanicalPanTiltZoom.ID] = { + [capabilities.mechanicalPanTiltZoom.commands.panRelative.NAME] = capability_handlers.ptz_relative_move_factory(camera_fields.PAN_IDX), + [capabilities.mechanicalPanTiltZoom.commands.tiltRelative.NAME] = capability_handlers.ptz_relative_move_factory(camera_fields.TILT_IDX), + [capabilities.mechanicalPanTiltZoom.commands.zoomRelative.NAME] = capability_handlers.ptz_relative_move_factory(camera_fields.ZOOM_IDX), + [capabilities.mechanicalPanTiltZoom.commands.setPan.NAME] = capability_handlers.ptz_set_position_factory(capabilities.mechanicalPanTiltZoom.commands.setPan), + [capabilities.mechanicalPanTiltZoom.commands.setTilt.NAME] = capability_handlers.ptz_set_position_factory(capabilities.mechanicalPanTiltZoom.commands.setTilt), + [capabilities.mechanicalPanTiltZoom.commands.setZoom.NAME] = capability_handlers.ptz_set_position_factory(capabilities.mechanicalPanTiltZoom.commands.setZoom), + [capabilities.mechanicalPanTiltZoom.commands.setPanTiltZoom.NAME] = capability_handlers.ptz_set_position_factory(capabilities.mechanicalPanTiltZoom.commands.setPanTiltZoom), + [capabilities.mechanicalPanTiltZoom.commands.savePreset.NAME] = capability_handlers.handle_save_preset, + [capabilities.mechanicalPanTiltZoom.commands.removePreset.NAME] = capability_handlers.handle_remove_preset, + [capabilities.mechanicalPanTiltZoom.commands.moveToPreset.NAME] = capability_handlers.handle_move_to_preset + }, + [capabilities.zoneManagement.ID] = { + [capabilities.zoneManagement.commands.newZone.NAME] = capability_handlers.handle_new_zone, + [capabilities.zoneManagement.commands.updateZone.NAME] = capability_handlers.handle_update_zone, + [capabilities.zoneManagement.commands.removeZone.NAME] = capability_handlers.handle_remove_zone, + [capabilities.zoneManagement.commands.createOrUpdateTrigger.NAME] = capability_handlers.handle_create_or_update_trigger, + [capabilities.zoneManagement.commands.removeTrigger.NAME] = capability_handlers.handle_remove_trigger, + [capabilities.zoneManagement.commands.setSensitivity.NAME] = capability_handlers.handle_set_sensitivity + }, + [capabilities.sounds.ID] = { + [capabilities.sounds.commands.playSound.NAME] = capability_handlers.handle_play_sound, + [capabilities.sounds.commands.setSelectedSound.NAME] = capability_handlers.handle_set_selected_sound + }, + [capabilities.videoStreamSettings.ID] = { + [capabilities.videoStreamSettings.commands.setStream.NAME] = capability_handlers.handle_set_stream + }, + [capabilities.cameraViewportSettings.ID] = { + [capabilities.cameraViewportSettings.commands.setDefaultViewport.NAME] = capability_handlers.handle_set_default_viewport + }, + [capabilities.localMediaStorage.ID] = { + [capabilities.localMediaStorage.commands.setLocalSnapshotRecording.NAME] = capability_handlers.set_enabled_factory(clusters.CameraAvStreamManagement.attributes.LocalSnapshotRecordingEnabled), + [capabilities.localMediaStorage.commands.setLocalVideoRecording.NAME] = capability_handlers.set_enabled_factory(clusters.CameraAvStreamManagement.attributes.LocalVideoRecordingEnabled) + } + }, + can_handle = require("sub_drivers.camera.can_handle") +} + +return camera_handler diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/eve_energy/can_handle.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/eve_energy/can_handle.lua new file mode 100644 index 0000000000..369b93b8de --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/eve_energy/can_handle.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local device_lib = require "st.device" +local fields = require "switch_utils.fields" +local switch_utils = require "switch_utils.utils" + +return function(opts, driver, device) + local EVE_MANUFACTURER_ID = 0x130A + -- this sub driver does NOT support child devices, and ONLY supports Eve devices + -- that do NOT support the Electrical Sensor device type + if device.network_type == device_lib.NETWORK_TYPE_MATTER and + device.manufacturer_info.vendor_id == EVE_MANUFACTURER_ID and + #switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.ELECTRICAL_SENSOR) == 0 then + return true, require("sub_drivers.eve_energy") + end + return false +end diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/eve_energy/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/eve_energy/init.lua index 04c2d7fc1d..8222fc1378 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/eve_energy/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/eve_energy/init.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 ------------------------------------------------------------------------------------- -- Definitions @@ -19,7 +8,7 @@ local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local cluster_base = require "st.matter.cluster_base" -local utils = require "st.utils" +local st_utils = require "st.utils" local data_types = require "st.matter.data_types" local device_lib = require "st.device" @@ -27,7 +16,6 @@ local SWITCH_INITIALIZED = "__switch_intialized" local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map" local ON_OFF_STATES = "ON_OFF_STATES" -local EVE_MANUFACTURER_ID = 0x130A local PRIVATE_CLUSTER_ID = 0x130AFC01 local PRIVATE_ATTR_ID_WATT = 0x130A000A @@ -47,16 +35,6 @@ local MINIMUM_ST_ENERGY_REPORT_INTERVAL = (15 * 60) -- 15 minutes, reported in s -- Eve specifics ------------------------------------------------------------------------------------- -local function is_eve_energy_products(opts, driver, device) - -- this sub driver does not support child devices - if device.network_type == device_lib.NETWORK_TYPE_MATTER and - device.manufacturer_info.vendor_id == EVE_MANUFACTURER_ID then - return true - end - - return false -end - -- Return a ISO 8061 formatted timestamp in UTC (Z) -- @return e.g. 2022-02-02T08:00:00Z local function epoch_to_iso8601(time) @@ -337,7 +315,7 @@ end local function watt_accumulated_attr_handler(driver, device, ib, zb_rx) if ib.data.value then local totalConsumptionRawValue = ib.data.value - local totalConsumptionWh = utils.round(1000 * totalConsumptionRawValue) + local totalConsumptionWh = st_utils.round(1000 * totalConsumptionRawValue) updateEnergyMeter(device, totalConsumptionWh) report_power_consumption_to_st_energy(device, totalConsumptionWh) end @@ -377,7 +355,7 @@ local eve_energy_handler = { capabilities.energyMeter, capabilities.powerConsumptionReport }, - can_handle = is_eve_energy_products + can_handle = require("sub_drivers.eve_energy.can_handle") } return eve_energy_handler diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/can_handle.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/can_handle.lua new file mode 100644 index 0000000000..1245fae5c9 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local switch_utils = require "switch_utils.utils" + +return function(opts, driver, device) + if switch_utils.get_product_override_field(device, "is_ikea_scroll") then + return true, require("sub_drivers.ikea_scroll") + end + return false +end diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua new file mode 100644 index 0000000000..6e21a56ac0 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua @@ -0,0 +1,50 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local switch_utils = require "switch_utils.utils" +local scroll_utils = require "sub_drivers.ikea_scroll.scroll_utils.utils" +local scroll_cfg = require "sub_drivers.ikea_scroll.scroll_utils.device_configuration" + +local IkeaScrollLifecycleHandlers = {} + +-- prevent main driver device_added handling from running +function IkeaScrollLifecycleHandlers.device_added(driver, device) +end + +function IkeaScrollLifecycleHandlers.device_init(driver, device) + device:set_endpoint_to_component_fn(switch_utils.endpoint_to_component) + device:extend_device("subscribe", scroll_utils.subscribe) + device:subscribe() +end + +function IkeaScrollLifecycleHandlers.do_configure(driver, device) + scroll_cfg.match_profile(driver, device) +end + +function IkeaScrollLifecycleHandlers.driver_switched(driver, device) + scroll_cfg.match_profile(driver, device) +end + +function IkeaScrollLifecycleHandlers.info_changed(driver, device, event, args) + if device.profile.id ~= args.old_st_store.profile.id then + scroll_cfg.configure_buttons(device) + device:subscribe() + end +end + + +-- DEVICE TEMPLATE -- + +local ikea_scroll_handler = { + NAME = "Ikea Scroll Handler", + lifecycle_handlers = { + added = IkeaScrollLifecycleHandlers.device_added, + doConfigure = IkeaScrollLifecycleHandlers.do_configure, + driverSwitched = IkeaScrollLifecycleHandlers.driver_switched, + infoChanged = IkeaScrollLifecycleHandlers.info_changed, + init = IkeaScrollLifecycleHandlers.device_init, + }, + can_handle = require("sub_drivers.ikea_scroll.can_handle") +} + +return ikea_scroll_handler diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/device_configuration.lua new file mode 100644 index 0000000000..cd2cee49ce --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/device_configuration.lua @@ -0,0 +1,35 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local clusters = require "st.matter.clusters" +local capabilities = require "st.capabilities" +local switch_utils = require "switch_utils.utils" +local switch_fields = require "switch_utils.fields" +local scroll_fields = require "sub_drivers.ikea_scroll.scroll_utils.fields" + +local IkeaScrollConfiguration = {} + +function IkeaScrollConfiguration.build_button_component_map(device) + local component_map = { + main = scroll_fields.ENDPOINTS_PRESS[1], + group2 = scroll_fields.ENDPOINTS_PRESS[2], + group3 = scroll_fields.ENDPOINTS_PRESS[3], + } + device:set_field(switch_fields.COMPONENT_TO_ENDPOINT_MAP, component_map, {persist = true}) +end + +function IkeaScrollConfiguration.configure_buttons(device) + for _, ep in ipairs(scroll_fields.ENDPOINTS_PRESS) do + device:send(clusters.Switch.attributes.MultiPressMax:read(device, ep)) + switch_utils.set_field_for_endpoint(device, switch_fields.SUPPORTS_MULTI_PRESS, ep, true, {persist = true}) + device:emit_event_for_endpoint(ep, capabilities.button.button.pushed({state_change = false})) + end +end + +function IkeaScrollConfiguration.match_profile(driver, device) + device:try_update_metadata({profile = "ikea-scroll"}) + IkeaScrollConfiguration.build_button_component_map(device) + IkeaScrollConfiguration.configure_buttons(device) +end + +return IkeaScrollConfiguration diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/fields.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/fields.lua new file mode 100644 index 0000000000..fff0a1cce4 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/fields.lua @@ -0,0 +1,21 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local clusters = require "st.matter.clusters" + +local IkeaScrollFields = {} + +-- PowerSource supported on Root Node +IkeaScrollFields.ENDPOINT_POWER_SOURCE = 0 + +-- Switch Endpoints used for basic press functionality +IkeaScrollFields.ENDPOINTS_PRESS = {3, 6, 9} + +-- Required Events for the ENDPOINTS_PRESS. +IkeaScrollFields.switch_press_subscribed_events = { + clusters.Switch.events.InitialPress.ID, + clusters.Switch.events.MultiPressComplete.ID, + clusters.Switch.events.LongPress.ID, +} + +return IkeaScrollFields diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/utils.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/utils.lua new file mode 100644 index 0000000000..67ba2acba5 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/utils.lua @@ -0,0 +1,26 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local im = require "st.matter.interaction_model" +local clusters = require "st.matter.clusters" +local scroll_fields = require "sub_drivers.ikea_scroll.scroll_utils.fields" + +local IkeaScrollUtils = {} + +-- override subscribe function to prevent subscribing to additional events from the main driver +function IkeaScrollUtils.subscribe(device) + local subscribe_request = im.InteractionRequest(im.InteractionRequest.RequestType.SUBSCRIBE, {}) + for _, ep_press in ipairs(scroll_fields.ENDPOINTS_PRESS) do + for _, switch_event in ipairs(scroll_fields.switch_press_subscribed_events) do + local ib = im.InteractionInfoBlock(ep_press, clusters.Switch.ID, nil, switch_event) + subscribe_request:with_info_block(ib) + end + end + local ib = im.InteractionInfoBlock( + scroll_fields.ENDPOINT_POWER_SOURCE, clusters.PowerSource.ID, clusters.PowerSource.attributes.BatPercentRemaining.ID + ) + subscribe_request:with_info_block(ib) + device:send(subscribe_request) +end + +return IkeaScrollUtils \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_mk1/can_handle.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_mk1/can_handle.lua new file mode 100644 index 0000000000..f3f5342989 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_mk1/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local device_lib = require "st.device" + +return function(opts, driver, device) + local THIRD_REALITY_MK1_FINGERPRINT = { vendor_id = 0x1407, product_id = 0x1388 } + if device.network_type == device_lib.NETWORK_TYPE_MATTER and + device.manufacturer_info.vendor_id == THIRD_REALITY_MK1_FINGERPRINT.vendor_id and + device.manufacturer_info.product_id == THIRD_REALITY_MK1_FINGERPRINT.product_id then + return true, require("sub_drivers.third_reality_mk1") + end + return false +end diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_mk1/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_mk1/init.lua index a579929fe0..1572886089 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_mk1/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_mk1/init.lua @@ -1,22 +1,9 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" -local device_lib = require "st.device" local im = require "st.matter.interaction_model" -local log = require "log" local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map" @@ -24,18 +11,6 @@ local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map" -- Third Reality MK1 specifics ------------------------------------------------------------------------------------- -local THIRD_REALITY_MK1_FINGERPRINT = { vendor_id = 0x1407, product_id = 0x1388 } - -local function is_third_reality_mk1(opts, driver, device) - if device.network_type == device_lib.NETWORK_TYPE_MATTER and - device.manufacturer_info.vendor_id == THIRD_REALITY_MK1_FINGERPRINT.vendor_id and - device.manufacturer_info.product_id == THIRD_REALITY_MK1_FINGERPRINT.product_id then - log.info("Using Third Reality MK1 sub driver") - return true - end - return false -end - local function endpoint_to_component(device, ep) local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {} for component, endpoint in pairs(map) do @@ -132,7 +107,7 @@ local third_reality_mk1_handler = { supported_capabilities = { capabilities.button }, - can_handle = is_third_reality_mk1 + can_handle = require("sub_drivers.third_reality_mk1.can_handle") } return third_reality_mk1_handler diff --git a/drivers/SmartThings/matter-switch/src/generic_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua similarity index 73% rename from drivers/SmartThings/matter-switch/src/generic_handlers/attribute_handlers.lua rename to drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index ad8a74a00f..e8dfe3f120 100644 --- a/drivers/SmartThings/matter-switch/src/generic_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -1,26 +1,22 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local version = require "version" local im = require "st.matter.interaction_model" - local st_utils = require "st.utils" -local fields = require "utils.switch_fields" -local switch_utils = require "utils.switch_utils" -local color_utils = require "utils.color_utils" +local fields = require "switch_utils.fields" +local switch_utils = require "switch_utils.utils" +local color_utils = require "switch_utils.color_utils" +local cfg = require "switch_utils.device_configuration" +local device_cfg = cfg.DeviceCfg + +-- Include driver-side definitions when lua libs api version is < 11 +if version.api < 11 then + clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement" + clusters.PowerTopology = require "embedded_clusters.PowerTopology" +end local AttributeHandlers = {} @@ -42,7 +38,10 @@ end function AttributeHandlers.level_control_current_level_handler(driver, device, ib, response) if ib.data.value ~= nil then - local level = math.floor((ib.data.value / 254.0 * 100) + 0.5) + local level = ib.data.value + if level > 0 then + level = math.max(1, st_utils.round(level / 254.0 * 100)) + end device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switchLevel.level(level)) if type(device.register_native_capability_attr_handler) == "function" then device:register_native_capability_attr_handler("switchLevel", "level") @@ -87,19 +86,23 @@ end -- [[ COLOR CONTROL CLUSTER ATTRIBUTES ]] -- function AttributeHandlers.current_hue_handler(driver, device, ib, response) - if device:get_field(fields.COLOR_MODE) == fields.X_Y_COLOR_MODE or ib.data.value == nil then - return + if device:get_field(fields.COLOR_MODE) ~= fields.X_Y_COLOR_MODE and ib.data.value ~= nil then + local hue = math.floor((ib.data.value / 0xFE * 100) + 0.5) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) + end + if type(device.register_native_capability_attr_handler) == "function" then + device:register_native_capability_attr_handler("colorControl", "hue") end - local hue = math.floor((ib.data.value / 0xFE * 100) + 0.5) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) end function AttributeHandlers.current_saturation_handler(driver, device, ib, response) - if device:get_field(fields.COLOR_MODE) == fields.X_Y_COLOR_MODE or ib.data.value == nil then - return + if device:get_field(fields.COLOR_MODE) ~= fields.X_Y_COLOR_MODE and ib.data.value ~= nil then + local sat = math.floor((ib.data.value / 0xFE * 100) + 0.5) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) + end + if type(device.register_native_capability_attr_handler) == "function" then + device:register_native_capability_attr_handler("colorControl", "saturation") end - local sat = math.floor((ib.data.value / 0xFE * 100) + 0.5) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) end function AttributeHandlers.color_temperature_mireds_handler(driver, device, ib, response) @@ -136,7 +139,7 @@ function AttributeHandlers.color_temperature_mireds_handler(driver, device, ib, end function AttributeHandlers.current_x_handler(driver, device, ib, response) - if device:get_field(fields.COLOR_MODE) == fields.HUE_SAT_COLOR_MODE then + if device:get_field(fields.COLOR_MODE) == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then return end local y = device:get_field(fields.RECEIVED_Y) @@ -154,7 +157,7 @@ function AttributeHandlers.current_x_handler(driver, device, ib, response) end function AttributeHandlers.current_y_handler(driver, device, ib, response) - if device:get_field(fields.COLOR_MODE) == fields.HUE_SAT_COLOR_MODE then + if device:get_field(fields.COLOR_MODE) == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then return end local x = device:get_field(fields.RECEIVED_X) @@ -170,15 +173,17 @@ function AttributeHandlers.current_y_handler(driver, device, ib, response) end function AttributeHandlers.color_mode_handler(driver, device, ib, response) - if ib.data.value == device:get_field(fields.COLOR_MODE) or (ib.data.value ~= fields.HUE_SAT_COLOR_MODE and ib.data.value ~= fields.X_Y_COLOR_MODE) then - return + if ib.data.value == device:get_field(fields.COLOR_MODE) + or (ib.data.value ~= clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION + and ib.data.value ~= clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) then + return end device:set_field(fields.COLOR_MODE, ib.data.value) local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) - if ib.data.value == fields.HUE_SAT_COLOR_MODE then + if ib.data.value == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then req:merge(clusters.ColorControl.attributes.CurrentHue:read()) req:merge(clusters.ColorControl.attributes.CurrentSaturation:read()) - elseif ib.data.value == fields.X_Y_COLOR_MODE then + elseif ib.data.value == clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY then req:merge(clusters.ColorControl.attributes.CurrentX:read()) req:merge(clusters.ColorControl.attributes.CurrentY:read()) end @@ -246,16 +251,11 @@ end function AttributeHandlers.active_power_handler(driver, device, ib, response) if ib.data.value then - local watt_value = ib.data.value / fields.CONVERSION_CONST_MILLIWATT_TO_WATT - if ib.endpoint_id ~= 0 then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.powerMeter.power({ value = watt_value, unit = "W"})) - else - -- when energy management is defined in the root endpoint(0), replace it with the first switch endpoint and process it. - device:emit_event_for_endpoint(device:get_field(fields.ENERGY_MANAGEMENT_ENDPOINT), capabilities.powerMeter.power({ value = watt_value, unit = "W"})) - end - if type(device.register_native_capability_attr_handler) == "function" then - device:register_native_capability_attr_handler("powerMeter","power") - end + local watt_value = ib.data.value / 1000 -- convert received milliwatt to watt + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.powerMeter.power({ value = watt_value, unit = "W"})) + end + if type(device.register_native_capability_attr_handler) == "function" then + device:register_native_capability_attr_handler("powerMeter","power") end end @@ -279,52 +279,92 @@ end -- [[ ELECTRICAL ENERGY MEASUREMENT CLUSTER ATTRIBUTES ]] -- -function AttributeHandlers.cumul_energy_imported_handler(driver, device, ib, response) - if ib.data.elements.energy then - local watt_hour_value = ib.data.elements.energy.value / fields.CONVERSION_CONST_MILLIWATT_TO_WATT - device:set_field(fields.TOTAL_IMPORTED_ENERGY, watt_hour_value, {persist = true}) - if ib.endpoint_id ~= 0 then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.energyMeter.energy({ value = watt_hour_value, unit = "Wh" })) +function AttributeHandlers.energy_imported_factory(is_periodic_report) + return function(driver, device, ib, response) + if version.api < 11 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct:augment_type(ib.data) + end + if ib.data.elements.energy then + local energy_imported_wh = ib.data.elements.energy.value / 1000 -- convert received milliwatt-hour to watt-hour + if is_periodic_report then + -- handle this report only if cumulative reports are not supported + if device:get_field(fields.CUMULATIVE_REPORTS_SUPPORTED) then return end + local energy_meter_latest_state = switch_utils.get_latest_state_for_endpoint( + device, ib, capabilities.energyMeter.ID, capabilities.energyMeter.energy.NAME + ) or 0 + energy_imported_wh = energy_imported_wh + energy_meter_latest_state + else + -- the field containing the offset may be associated with a child device + local field_device = switch_utils.find_child(device, ib.endpoint_id) or device + local energy_meter_offset = field_device:get_field(fields.ENERGY_METER_OFFSET) or 0.0 + energy_imported_wh = energy_imported_wh - energy_meter_offset + end + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.energyMeter.energy({ value = energy_imported_wh, unit = "Wh" })) + switch_utils.report_power_consumption_to_st_energy(device, ib.endpoint_id, energy_imported_wh) else - -- when energy management is defined in the root endpoint(0), replace it with the first switch endpoint and process it. - device:emit_event_for_endpoint(device:get_field(fields.ENERGY_MANAGEMENT_ENDPOINT), capabilities.energyMeter.energy({ value = watt_hour_value, unit = "Wh" })) + device.log.warn("Received data from the energy imported attribute does not include a numerical energy value") end - switch_utils.report_power_consumption_to_st_energy(device, device:get_field(fields.TOTAL_IMPORTED_ENERGY)) end end -function AttributeHandlers.per_energy_imported_handler(driver, device, ib, response) - if ib.data.elements.energy then - local watt_hour_value = ib.data.elements.energy.value / fields.CONVERSION_CONST_MILLIWATT_TO_WATT - local latest_energy_report = device:get_field(fields.TOTAL_IMPORTED_ENERGY) or 0 - local summed_energy_report = latest_energy_report + watt_hour_value - device:set_field(fields.TOTAL_IMPORTED_ENERGY, summed_energy_report, {persist = true}) - device:emit_event(capabilities.energyMeter.energy({ value = summed_energy_report, unit = "Wh" })) - switch_utils.report_power_consumption_to_st_energy(device, device:get_field(fields.TOTAL_IMPORTED_ENERGY)) + +-- [[ POWER TOPOLOGY CLUSTER ATTRIBUTES ]] -- + +--- AvailableEndpoints: This attribute SHALL indicate the list of endpoints capable of +--- providing power to and/or consuming power from the endpoint hosting this server. +--- +--- In the case there are multiple endpoints supporting the PowerTopology cluster with +--- SET feature, all AvailableEndpoints responses must be handled before profiling. +function AttributeHandlers.available_endpoints_handler(driver, device, ib, response) + local set_topology_eps = device:get_field(fields.ELECTRICAL_SENSOR_EPS) + for i, set_ep_info in pairs(set_topology_eps or {}) do + if ib.endpoint_id == set_ep_info.endpoint_id then + -- since EP reponse is being handled here, remove it from the ELECTRICAL_SENSOR_EPS table + switch_utils.remove_field_index(device, fields.ELECTRICAL_SENSOR_EPS, i) + local available_endpoints_ids = {} + for _, element in pairs(ib.data.elements) do + table.insert(available_endpoints_ids, element.value) + end + -- set the required profile elements ("-power", etc.) to one of these EP IDs for later profiling. + -- set an assigned child key in the case this will emit events on an EDGE_CHILD device + switch_utils.set_fields_for_electrical_sensor_endpoint(device, set_ep_info, available_endpoints_ids) + break + end + end + if #set_topology_eps == 0 then -- in other words, all AvailableEndpoints attribute responses have been handled + device:set_field(fields.profiling_data.POWER_TOPOLOGY, clusters.PowerTopology.types.Feature.SET_TOPOLOGY, {persist=true}) + device_cfg.match_profile(driver, device) end end -function AttributeHandlers.energy_imported_factory(is_cumulative_report) - return function(driver, device, ib, response) - -- workaround: ignore devices supporting Eve's private energy cluster AND the ElectricalEnergyMeasurement cluster - local EVE_MANUFACTURER_ID, EVE_PRIVATE_CLUSTER_ID = 0x130A, 0x130AFC01 - local eve_private_energy_eps = device:get_endpoints(EVE_PRIVATE_CLUSTER_ID) - if device.manufacturer_info.vendor_id == EVE_MANUFACTURER_ID and #eve_private_energy_eps > 0 then - return - end - if is_cumulative_report then - AttributeHandlers.cumul_energy_imported_handler(driver, device, ib, response) - elseif device:get_field(fields.CUMULATIVE_REPORTS_NOT_SUPPORTED) then - AttributeHandlers.per_energy_imported_handler(driver, device, ib, response) +-- [[ DESCRIPTOR CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.parts_list_handler(driver, device, ib, response) + local tree_topology_eps = device:get_field(fields.ELECTRICAL_SENSOR_EPS) + for i, tree_ep_info in pairs(tree_topology_eps or {}) do + if ib.endpoint_id == tree_ep_info.endpoint_id then + -- since EP reponse is being handled here, remove it from the ELECTRICAL_SENSOR_EPS table + switch_utils.remove_field_index(device, fields.ELECTRICAL_SENSOR_EPS, i) + local associated_endpoints_ids = {} + for _, element in pairs(ib.data.elements) do + table.insert(associated_endpoints_ids, element.value) + end + -- set the required profile elements ("-power", etc.) to one of these EP IDs for later profiling. + -- set an assigned child key in the case this will emit events on an EDGE_CHILD device + switch_utils.set_fields_for_electrical_sensor_endpoint(device, tree_ep_info, associated_endpoints_ids) + break end end + if #tree_topology_eps == 0 then -- in other words, all PartsList attribute responses for TREE Electrical Sensor EPs have been handled + device:set_field(fields.profiling_data.POWER_TOPOLOGY, clusters.PowerTopology.types.Feature.TREE_TOPOLOGY, {persist=true}) + device_cfg.match_profile(driver, device) + end end -- [[ POWER SOURCE CLUSTER ATTRIBUTES ]] -- - function AttributeHandlers.bat_percent_remaining_handler(driver, device, ib, response) if ib.data.value then device:emit_event(capabilities.battery.battery(math.floor(ib.data.value / 2.0 + 0.5))) @@ -361,8 +401,7 @@ function AttributeHandlers.power_source_attribute_list_handler(driver, device, i profile_name = string.format("%d-", #button_eps) .. profile_name end - if device.manufacturer_info.vendor_id == fields.AQARA_MANUFACTURER_ID and - device.manufacturer_info.product_id == fields.AQARA_CLIMATE_SENSOR_W100_ID then + if switch_utils.get_product_override_field(device, "is_climate_sensor_w100") then profile_name = profile_name .. "-temperature-humidity" end device:try_update_metadata({ profile = profile_name }) @@ -505,4 +544,4 @@ function AttributeHandlers.percent_current_handler(driver, device, ib, response) device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanSpeedPercent.percent(ib.data.value)) end -return AttributeHandlers \ No newline at end of file +return AttributeHandlers diff --git a/drivers/SmartThings/matter-switch/src/generic_handlers/capability_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua similarity index 82% rename from drivers/SmartThings/matter-switch/src/generic_handlers/capability_handlers.lua rename to drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua index 05a781bc3d..f96686b73a 100644 --- a/drivers/SmartThings/matter-switch/src/generic_handlers/capability_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua @@ -1,23 +1,12 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local st_utils = require "st.utils" -local switch_utils = require "utils.switch_utils" -local fields = require "utils.switch_fields" local version = require "version" +local switch_utils = require "switch_utils.utils" +local fields = require "switch_utils.fields" local CapabilityHandlers = {} @@ -53,7 +42,7 @@ function CapabilityHandlers.handle_switch_set_level(driver, device, cmd) device:register_native_capability_cmd_handler(cmd.capability, cmd.command) end local endpoint_id = device:component_to_endpoint(cmd.component) - local level = math.floor(cmd.args.level/100.0 * 254) + local level = st_utils.round(cmd.args.level/100.0 * 254) device:send(clusters.LevelControl.server.commands.MoveToLevelWithOnOff(device, endpoint_id, level, cmd.args.rate, 0, 0)) end @@ -179,4 +168,25 @@ function CapabilityHandlers.handle_fan_speed_set_percent(driver, device, cmd) device:send(clusters.FanControl.attributes.PercentSetting:write(device, fan_ep, speed)) end -return CapabilityHandlers \ No newline at end of file + +-- [[ ENERGY METER CAPABILITY COMMANDS ]] -- + +--- +--- If a Cumulative Reporting device, this will store the most recent energy meter reading, and all subsequent reports will have this value subtracted +--- from the value reported. Matter, like Zigbee and unlike Z-Wave, does not provide a way to reset the value to zero, so this is an attempt at a workaround. +--- In the case it is a Periodic Reporting device, the reports do not need to be offset, so setting the current energy to 0.0 will do the same thing. +--- +function CapabilityHandlers.handle_reset_energy_meter(driver, device, cmd) + local energy_meter_latest_state = device:get_latest_state(cmd.component, capabilities.energyMeter.ID, capabilities.energyMeter.energy.NAME) or 0.0 + if energy_meter_latest_state ~= 0.0 then + device:emit_component_event(device.profile.components[cmd.component], capabilities.energyMeter.energy({value = 0.0, unit = "Wh"})) + -- note: field containing cumulative reports supported is only set on the parent device + local field_device = device:get_parent_device() or device + if field_device:get_field(fields.CUMULATIVE_REPORTS_SUPPORTED) then + local current_offset = device:get_field(fields.ENERGY_METER_OFFSET) or 0.0 + device:set_field(fields.ENERGY_METER_OFFSET, current_offset + energy_meter_latest_state, {persist=true}) + end + end +end + +return CapabilityHandlers diff --git a/drivers/SmartThings/matter-switch/src/generic_handlers/event_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/event_handlers.lua similarity index 85% rename from drivers/SmartThings/matter-switch/src/generic_handlers/event_handlers.lua rename to drivers/SmartThings/matter-switch/src/switch_handlers/event_handlers.lua index c16a3bd2d5..16177b38f4 100644 --- a/drivers/SmartThings/matter-switch/src/generic_handlers/event_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/event_handlers.lua @@ -1,21 +1,10 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local lua_socket = require "socket" -local fields = require "utils.switch_fields" -local switch_utils = require "utils.switch_utils" +local fields = require "switch_utils.fields" +local switch_utils = require "switch_utils.utils" local EventHandlers = {} diff --git a/drivers/SmartThings/matter-switch/src/utils/color_utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/color_utils.lua similarity index 77% rename from drivers/SmartThings/matter-switch/src/utils/color_utils.lua rename to drivers/SmartThings/matter-switch/src/switch_utils/color_utils.lua index 410920258a..7e4c231a30 100644 --- a/drivers/SmartThings/matter-switch/src/utils/color_utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/color_utils.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 --TODO remove the usage of these color utils once 0.48.x has been distributed -- to all hubs. diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua new file mode 100644 index 0000000000..ffea9efa2e --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -0,0 +1,257 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local version = require "version" +local fields = require "switch_utils.fields" +local switch_utils = require "switch_utils.utils" +local embedded_cluster_utils = require "switch_utils.embedded_cluster_utils" + +-- Include driver-side definitions when lua libs api version is < 11 +if version.api < 11 then + clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement" + clusters.ElectricalPowerMeasurement = require "embedded_clusters.ElectricalPowerMeasurement" + clusters.ValveConfigurationAndControl = require "embedded_clusters.ValveConfigurationAndControl" +end + +local DeviceConfiguration = {} +local ChildConfiguration = {} +local SwitchDeviceConfiguration = {} +local ButtonDeviceConfiguration = {} +local FanDeviceConfiguration = {} + +function ChildConfiguration.create_or_update_child_devices(driver, device, server_cluster_ep_ids, default_endpoint_id, assign_profile_fn) + if #server_cluster_ep_ids == 1 and server_cluster_ep_ids[1] == default_endpoint_id then -- no children will be created + return + end + + table.sort(server_cluster_ep_ids) + for device_num, ep_id in ipairs(server_cluster_ep_ids) do + if ep_id ~= default_endpoint_id then -- don't create a child device that maps to the main endpoint + local label_and_name = string.format("%s %d", device.label, device_num) + local child_profile, _ = assign_profile_fn(device, ep_id, true) + local existing_child_device = device:get_field(fields.IS_PARENT_CHILD_DEVICE) and switch_utils.find_child(device, ep_id) + if not existing_child_device then + driver:try_create_device({ + type = "EDGE_CHILD", + label = label_and_name, + profile = child_profile, + parent_device_id = device.id, + parent_assigned_child_key = string.format("%d", ep_id), + vendor_provided_label = label_and_name + }) + else + existing_child_device:try_update_metadata({ + profile = child_profile + }) + end + end + end + + -- Persist so that the find_child function is always set on each driver init. + device:set_field(fields.IS_PARENT_CHILD_DEVICE, true, {persist = true}) + device:set_find_child(switch_utils.find_child) +end + +function FanDeviceConfiguration.assign_profile_for_fan_ep(device, server_fan_ep_id) + local ep_info = switch_utils.get_endpoint_info(device, server_fan_ep_id) + local fan_cluster_info = switch_utils.find_cluster_on_ep(ep_info, clusters.FanControl.ID) + local optional_supported_component_capabilities = {} + local main_component_capabilities = {} + + if clusters.FanControl.are_features_supported(clusters.FanControl.types.Feature.MULTI_SPEED, fan_cluster_info.feature_map) then + table.insert(main_component_capabilities, capabilities.fanSpeedPercent.ID) + -- only fanMode can trigger AUTO, so a multi-speed fan still requires this capability if it supports AUTO + if clusters.FanControl.are_features_supported(clusters.FanControl.types.Feature.AUTO, fan_cluster_info.feature_map) then + table.insert(main_component_capabilities, capabilities.fanMode.ID) + end + else -- MULTI_SPEED is not supported + table.insert(main_component_capabilities, capabilities.fanMode.ID) + end + + table.insert(optional_supported_component_capabilities, {"main", main_component_capabilities}) + return "fan-modular", optional_supported_component_capabilities +end + + +function SwitchDeviceConfiguration.assign_profile_for_onoff_ep(device, server_onoff_ep_id, is_child_device) + local ep_info = switch_utils.get_endpoint_info(device, server_onoff_ep_id) + + -- per spec, the Switch device types support OnOff as CLIENT, though some vendors break spec and support it as SERVER. + local primary_dt_id = switch_utils.find_max_subset_device_type(ep_info, fields.DEVICE_TYPE_ID.LIGHT) + or switch_utils.find_max_subset_device_type(ep_info, fields.DEVICE_TYPE_ID.SWITCH) + or switch_utils.find_primary_device_type(ep_info) + + local generic_profile = fields.device_type_profile_map[primary_dt_id] + + local static_electrical_tags = switch_utils.get_field_for_endpoint(device, fields.ELECTRICAL_TAGS, server_onoff_ep_id) + if static_electrical_tags ~= nil then + -- profiles like 'light-binary' and 'plug-binary' should drop the '-binary' and become 'light-power', 'plug-energy-powerConsumption', etc. + generic_profile = string.gsub(generic_profile, "-binary", "") .. static_electrical_tags + end + + if is_child_device and generic_profile == switch_utils.get_product_override_field(device, "initial_profile") then + generic_profile = switch_utils.get_product_override_field(device, "target_profile") or generic_profile + end + + -- if no supported device type is found, return switch-binary as a generic "OnOff EP" profile + return generic_profile or "switch-binary" +end + +-- Per the spec, these attributes are "meant to be changed only during commissioning." +function SwitchDeviceConfiguration.set_device_control_options(device) + for _, ep in ipairs(device.endpoints) do + -- before the Matter 1.3 lua libs update (HUB FW 54), OptionsBitmap was defined as LevelControlOptions + if switch_utils.find_cluster_on_ep(ep, clusters.LevelControl.ID) then + device:send(clusters.LevelControl.attributes.Options:write(device, ep.endpoint_id, clusters.LevelControl.types.LevelControlOptions.EXECUTE_IF_OFF)) + end + -- before the Matter 1.4 lua libs update (HUB FW 56), there was no OptionsBitmap type defined + if switch_utils.find_cluster_on_ep(ep, clusters.ColorControl.ID) then + local excute_if_off_bit = clusters.ColorControl.types.OptionsBitmap and clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF or 0x0001 + device:send(clusters.ColorControl.attributes.Options:write(device, ep.endpoint_id, excute_if_off_bit)) + end + end +end + +function ButtonDeviceConfiguration.update_button_profile(device, default_endpoint_id, num_button_eps) + local profile_name = string.gsub(num_button_eps .. "-button", "1%-", "") -- remove the "1-" in a device with 1 button ep + if switch_utils.device_type_supports_button_switch_combination(device, default_endpoint_id) then + profile_name = "light-level-" .. profile_name + end + local motion_eps = device:get_endpoints(clusters.OccupancySensing.ID) + if #motion_eps > 0 and (num_button_eps == 3 or num_button_eps == 6) then -- only these two devices are handled + profile_name = profile_name .. "-motion" + end + local battery_supported = #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) > 0 + if battery_supported then -- battery profiles are configured later, in power_source_attribute_list_handler + device:send(clusters.PowerSource.attributes.AttributeList:read(device)) + else + device:try_update_metadata({profile = profile_name}) + end +end + +function ButtonDeviceConfiguration.update_button_component_map(device, default_endpoint_id, button_eps) + -- create component mapping on the main profile button endpoints + table.sort(button_eps) + local component_map = {} + component_map["main"] = default_endpoint_id + for component_num, ep in ipairs(button_eps) do + if ep ~= default_endpoint_id then + local button_component = "button" + if #button_eps > 1 then + button_component = button_component .. component_num + end + component_map[button_component] = ep + end + end + device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, component_map, {persist = true}) +end + + +function ButtonDeviceConfiguration.configure_buttons(device, momentary_switch_ep_ids) + local msr_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_RELEASE}) + local msl_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_LONG_PRESS}) + local msm_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS}) + + for _, ep in ipairs(momentary_switch_ep_ids or {}) do + if device.profile.components[switch_utils.endpoint_to_component(device, ep)] then + device.log.info_with({hub_logs=true}, string.format("Configuring Supported Values for generic switch endpoint %d", ep)) + local supportedButtonValues_event + -- this ordering is important, since MSM & MSL devices must also support MSR + if switch_utils.tbl_contains(msm_eps, ep) then + supportedButtonValues_event = nil -- deferred to the max press handler + device:send(clusters.Switch.attributes.MultiPressMax:read(device, ep)) + switch_utils.set_field_for_endpoint(device, fields.SUPPORTS_MULTI_PRESS, ep, true, {persist = true}) + elseif switch_utils.tbl_contains(msl_eps, ep) then + supportedButtonValues_event = capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}) + elseif switch_utils.tbl_contains(msr_eps, ep) then + supportedButtonValues_event = capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}) + switch_utils.set_field_for_endpoint(device, fields.EMULATE_HELD, ep, true, {persist = true}) + else -- this switch endpoint only supports momentary switch, no release events + supportedButtonValues_event = capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}) + switch_utils.set_field_for_endpoint(device, fields.INITIAL_PRESS_ONLY, ep, true, {persist = true}) + end + + if supportedButtonValues_event then + device:emit_event_for_endpoint(ep, supportedButtonValues_event) + end + device:emit_event_for_endpoint(ep, capabilities.button.button.pushed({state_change = false})) + else + device.log.info_with({hub_logs=true}, string.format("Component not found for generic switch endpoint %d. Skipping Supported Value configuration", ep)) + end + end +end + + +-- [[ PROFILE MATCHING AND CONFIGURATIONS ]] -- + +local function profiling_data_still_required(device) + for _, field in pairs(fields.profiling_data) do + if device:get_field(field) == nil then + return true -- data still required if a field is nil + end + end + return false +end + +function DeviceConfiguration.match_profile(driver, device) + if profiling_data_still_required(device) then return end + + local default_endpoint_id = switch_utils.find_default_endpoint(device) + local optional_component_capabilities + local updated_profile + + if #embedded_cluster_utils.get_endpoints(device, clusters.ValveConfigurationAndControl.ID) > 0 then + updated_profile = "water-valve" + if #embedded_cluster_utils.get_endpoints(device, clusters.ValveConfigurationAndControl.ID, + {feature_bitmap = clusters.ValveConfigurationAndControl.types.Feature.LEVEL}) > 0 then + updated_profile = updated_profile .. "-level" + end + end + + local server_onoff_ep_ids = device:get_endpoints(clusters.OnOff.ID) -- get_endpoints defaults to return EPs supporting SERVER or BOTH + if #server_onoff_ep_ids > 0 then + ChildConfiguration.create_or_update_child_devices(driver, device, server_onoff_ep_ids, default_endpoint_id, SwitchDeviceConfiguration.assign_profile_for_onoff_ep) + end + + if switch_utils.tbl_contains(server_onoff_ep_ids, default_endpoint_id) then + updated_profile = SwitchDeviceConfiguration.assign_profile_for_onoff_ep(device, default_endpoint_id) + local generic_profile = function(s) return string.find(updated_profile or "", s, 1, true) end + if generic_profile("light-level") and #device:get_endpoints(clusters.OccupancySensing.ID) > 0 then + updated_profile = "light-level-motion" + elseif switch_utils.check_switch_category_vendor_overrides(device) then + -- check whether the overwrite should be over "plug" or "light" based on the current profile + local overwrite_category = string.find(updated_profile, "plug") and "plug" or "light" + updated_profile = string.gsub(updated_profile, overwrite_category, "switch") + elseif generic_profile("light-level-colorTemperature") or generic_profile("light-color-level") then + -- ignore attempts to dynamically profile light-level-colorTemperature and light-color-level devices for now, since + -- these may lose fingerprinted Kelvin ranges when dynamically profiled. + return + end + end + + local fan_device_type_ep_ids = switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.FAN) + if #fan_device_type_ep_ids > 0 then + updated_profile, optional_component_capabilities = FanDeviceConfiguration.assign_profile_for_fan_ep(device, default_endpoint_id) + device:set_field(fields.MODULAR_PROFILE_UPDATED, true) + end + + -- initialize the main device card with buttons if applicable + local momemtary_switch_ep_ids = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}) + if switch_utils.tbl_contains(fields.STATIC_BUTTON_PROFILE_SUPPORTED, #momemtary_switch_ep_ids) then + ButtonDeviceConfiguration.update_button_profile(device, default_endpoint_id, #momemtary_switch_ep_ids) + -- All button endpoints found will be added as additional components in the profile containing the default_endpoint_id. + ButtonDeviceConfiguration.update_button_component_map(device, default_endpoint_id, momemtary_switch_ep_ids) + ButtonDeviceConfiguration.configure_buttons(device, momemtary_switch_ep_ids) + return + end + + device:try_update_metadata({ profile = updated_profile, optional_component_capabilities = optional_component_capabilities }) +end + +return { + DeviceCfg = DeviceConfiguration, + SwitchCfg = SwitchDeviceConfiguration, + ButtonCfg = ButtonDeviceConfiguration +} \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/src/utils/embedded_cluster_utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/embedded_cluster_utils.lua similarity index 79% rename from drivers/SmartThings/matter-switch/src/utils/embedded_cluster_utils.lua rename to drivers/SmartThings/matter-switch/src/switch_utils/embedded_cluster_utils.lua index 29af9feebd..8f52a1488c 100644 --- a/drivers/SmartThings/matter-switch/src/utils/embedded_cluster_utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/embedded_cluster_utils.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.matter.clusters" local utils = require "st.utils" diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua new file mode 100644 index 0000000000..ab878698ad --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/switch_utils/fields.lua @@ -0,0 +1,185 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local SwitchFields = {} + +SwitchFields.MOST_RECENT_TEMP = "mostRecentTemp" +SwitchFields.RECEIVED_X = "receivedX" +SwitchFields.RECEIVED_Y = "receivedY" +SwitchFields.HUESAT_SUPPORT = "huesatSupport" + +SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT = 1000000 + +-- These values are a "sanity check" to check that values we are getting are reasonable +local COLOR_TEMPERATURE_KELVIN_MAX = 15000 +local COLOR_TEMPERATURE_KELVIN_MIN = 1000 +SwitchFields.COLOR_TEMPERATURE_MIRED_MAX = SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MIN +SwitchFields.COLOR_TEMPERATURE_MIRED_MIN = SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MAX + +SwitchFields.SWITCH_LEVEL_LIGHTING_MIN = 1 +SwitchFields.CURRENT_HUESAT_ATTR_MIN = 0 +SwitchFields.CURRENT_HUESAT_ATTR_MAX = 254 + +SwitchFields.DEVICE_TYPE_ID = { + AGGREGATOR = 0x000E, + BRIDGED_NODE = 0x0013, + CAMERA = 0x0142, + CHIME = 0x0146, + DIMMABLE_PLUG_IN_UNIT = 0x010B, + DOORBELL = 0x0143, + ELECTRICAL_SENSOR = 0x0510, + FAN = 0x002B, + GENERIC_SWITCH = 0x000F, + MOUNTED_ON_OFF_CONTROL = 0x010F, + MOUNTED_DIMMABLE_LOAD_CONTROL = 0x0110, + ON_OFF_PLUG_IN_UNIT = 0x010A, + LIGHT = { + ON_OFF = 0x0100, + DIMMABLE = 0x0101, + COLOR_TEMPERATURE = 0x010C, + EXTENDED_COLOR = 0x010D, + }, + SWITCH = { + ON_OFF_LIGHT = 0x0103, + DIMMER = 0x0104, + COLOR_DIMMER = 0x0105, + }, +} + +SwitchFields.device_type_profile_map = { + [SwitchFields.DEVICE_TYPE_ID.LIGHT.ON_OFF] = "light-binary", + [SwitchFields.DEVICE_TYPE_ID.LIGHT.DIMMABLE] = "light-level", + [SwitchFields.DEVICE_TYPE_ID.LIGHT.COLOR_TEMPERATURE] = "light-level-colorTemperature", + [SwitchFields.DEVICE_TYPE_ID.LIGHT.EXTENDED_COLOR] = "light-color-level", + [SwitchFields.DEVICE_TYPE_ID.SWITCH.ON_OFF_LIGHT] = "switch-binary", + [SwitchFields.DEVICE_TYPE_ID.SWITCH.DIMMER] = "switch-level", + [SwitchFields.DEVICE_TYPE_ID.SWITCH.COLOR_DIMMER] = "switch-color-level", + [SwitchFields.DEVICE_TYPE_ID.ON_OFF_PLUG_IN_UNIT] = "plug-binary", + [SwitchFields.DEVICE_TYPE_ID.DIMMABLE_PLUG_IN_UNIT] = "plug-level", + [SwitchFields.DEVICE_TYPE_ID.MOUNTED_ON_OFF_CONTROL] = "switch-binary", + [SwitchFields.DEVICE_TYPE_ID.MOUNTED_DIMMABLE_LOAD_CONTROL] = "switch-level", +} + +-- COMPONENT_TO_ENDPOINT_MAP is here to preserve the endpoint mapping for +-- devices that were joined to this driver as MCD devices before the transition +-- to join switch devices as parent-child. This value will exist in the device +-- table for devices that joined prior to this transition, and is also used for +-- button devices that require component mapping. +SwitchFields.COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map" +SwitchFields.IS_PARENT_CHILD_DEVICE = "__is_parent_child_device" + +--- If the ASSIGNED_CHILD_KEY field is populated for an endpoint, it should be +--- used as the key in the get_child_by_parent_assigned_key() function. This allows +--- multiple endpoints to associate with the same child device, though right now child +--- devices are keyed using only one endpoint id. +SwitchFields.ASSIGNED_CHILD_KEY = "__assigned_child_key" + +SwitchFields.COLOR_TEMP_BOUND_RECEIVED_KELVIN = "__colorTemp_bound_received_kelvin" +SwitchFields.COLOR_TEMP_BOUND_RECEIVED_MIRED = "__colorTemp_bound_received_mired" +SwitchFields.COLOR_TEMP_MIN = "__color_temp_min" +SwitchFields.COLOR_TEMP_MAX = "__color_temp_max" +SwitchFields.LEVEL_BOUND_RECEIVED = "__level_bound_received" +SwitchFields.LEVEL_MIN = "__level_min" +SwitchFields.LEVEL_MAX = "__level_max" +SwitchFields.COLOR_MODE = "__color_mode" + +SwitchFields.SUBSCRIBED_ATTRIBUTES_KEY = "__subscribed_attributes" + +SwitchFields.updated_fields = { + { current_field_name = "__component_to_endpoint_map_button", updated_field_name = SwitchFields.COMPONENT_TO_ENDPOINT_MAP }, + { current_field_name = "__switch_intialized", updated_field_name = nil }, + { current_field_name = "__energy_management_endpoint", updated_field_name = nil }, + { current_field_name = "__total_imported_energy", updated_field_name = nil }, +} + +SwitchFields.vendor_overrides = { + [0x1321] = { -- SONOFF_MANUFACTURER_ID + [0x000C] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, + [0x000D] = { target_profile = "switch-binary", initial_profile = "plug-binary" }, + }, + [0x115F] = { -- AQARA_MANUFACTURER_ID + [0x1006] = { ignore_combo_switch_button = true }, -- 3 Buttons(Generic Switch), 1 Channel (Dimmable Light) + [0x100A] = { ignore_combo_switch_button = true }, -- 1 Buttons(Generic Switch), 1 Channel (Dimmable Light) + [0x2004] = { is_climate_sensor_w100 = true }, -- Climate Sensor W100, requires unique profile + }, + [0x117C] = { -- IKEA_MANUFACTURER_ID + [0x8000] = { is_ikea_scroll = true } + }, +} + +SwitchFields.switch_category_vendor_overrides = { + [0x1432] = -- Elko + {0x1000}, + [0x130A] = -- Eve + {0x005D, 0x0043}, + [0x1339] = -- GE + {0x007D, 0x0074, 0x0075}, + [0x1372] = -- Innovation Matters + {0x0002}, + [0x1189] = -- Ledvance + {0x0891, 0x0892}, + [0x1021] = -- Legrand + {0x0005}, + [0x109B] = -- Leviton + {0x1001, 0x1000, 0x100B, 0x100E, 0x100C, 0x100D, 0x1009, 0x1003, 0x1004, 0x1002}, + [0x142B] = -- LeTianPai + {0x1004, 0x1003, 0x1002}, + [0x1509] = -- SmartSetup + {0x0004, 0x0001}, + [0x1321] = -- SONOFF + {0x000B, 0x000C, 0x000D}, + [0x147F] = -- U-Tec + {0x0004}, + [0x139C] = -- Zemismart + {0xEEE2, 0xAB08, 0xAB31, 0xAB04, 0xAB01, 0xAB43, 0xAB02, 0xAB03, 0xAB05} +} + +--- stores a table of endpoints that support the Electrical Sensor device type, used during profiling +--- in AvailableEndpoints and PartsList handlers for SET and TREE PowerTopology features, respectively +SwitchFields.ELECTRICAL_SENSOR_EPS = "__electrical_sensor_eps" + +--- used in tandem with an EP ID. Stores the required electrical tags "-power", "-energy-powerConsumption", etc. +--- for an Electrical Sensor EP with a "primary" endpoint, used during device profling. +SwitchFields.ELECTRICAL_TAGS = "__electrical_tags" + +SwitchFields.MODULAR_PROFILE_UPDATED = "__modular_profile_updated" + +SwitchFields.profiling_data = { + POWER_TOPOLOGY = "__power_topology", +} + +SwitchFields.ENERGY_METER_OFFSET = "__energy_meter_offset" +SwitchFields.CUMULATIVE_REPORTS_SUPPORTED = "__cumulative_reports_supported" +SwitchFields.LAST_IMPORTED_REPORT_TIMESTAMP = "__last_imported_report_timestamp" +SwitchFields.MINIMUM_ST_ENERGY_REPORT_INTERVAL = (15 * 60) -- 15 minutes, reported in seconds + +SwitchFields.START_BUTTON_PRESS = "__start_button_press" +SwitchFields.TIMEOUT_THRESHOLD = 10 --arbitrary timeout +SwitchFields.HELD_THRESHOLD = 1 + +-- this is the number of buttons for which we have a static profile already made +SwitchFields.STATIC_BUTTON_PROFILE_SUPPORTED = {1, 2, 3, 4, 5, 6, 7, 8, 9} + +-- Some switches will send a MultiPressComplete event as part of a long press sequence. Normally the driver will create a +-- button capability event on receipt of MultiPressComplete, but in this case that would result in an extra event because +-- the "held" capability event is generated when the LongPress event is received. The IGNORE_NEXT_MPC flag is used +-- to tell the driver to ignore MultiPressComplete if it is received after a long press to avoid this extra event. +SwitchFields.IGNORE_NEXT_MPC = "__ignore_next_mpc" + +-- These are essentially storing the supported features of a given endpoint +-- TODO: add an is_feature_supported_for_endpoint function to matter.device that takes an endpoint +SwitchFields.EMULATE_HELD = "__emulate_held" -- for non-MSR (MomentarySwitchRelease) devices we can emulate this on the software side +SwitchFields.SUPPORTS_MULTI_PRESS = "__multi_button" -- for MSM devices (MomentarySwitchMultiPress), create an event on receipt of MultiPressComplete +SwitchFields.INITIAL_PRESS_ONLY = "__initial_press_only" -- for devices that support MS (MomentarySwitch), but not MSR (MomentarySwitchRelease) + +SwitchFields.TEMP_BOUND_RECEIVED = "__temp_bound_received" +SwitchFields.TEMP_MIN = "__temp_min" +SwitchFields.TEMP_MAX = "__temp_max" + +SwitchFields.TRANSITION_TIME = 0 --1/10ths of a second +-- When sent with a command, these options mask and override bitmaps cause the command +-- to take effect when the switch/light is off. +SwitchFields.OPTIONS_MASK = 0x01 +SwitchFields.OPTIONS_OVERRIDE = 0x01 + +return SwitchFields diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua new file mode 100644 index 0000000000..e9809d4e46 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -0,0 +1,501 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local MatterDriver = require "st.matter.driver" +local fields = require "switch_utils.fields" +local st_utils = require "st.utils" +local version = require "version" +local clusters = require "st.matter.clusters" +local capabilities = require "st.capabilities" +local im = require "st.matter.interaction_model" +local log = require "log" + +-- Include driver-side definitions when lua libs api version is < 11 +if version.api < 11 then + clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement" + clusters.ElectricalPowerMeasurement = require "embedded_clusters.ElectricalPowerMeasurement" + clusters.PowerTopology = require "embedded_clusters.PowerTopology" +end + +if version.api < 16 then + clusters.Descriptor = require "embedded_clusters.Descriptor" +end + +local utils = {} + +function utils.tbl_contains(array, value) + if value == nil then return false end + for _, element in pairs(array or {}) do + if element == value then + return true + end + end + return false +end + +function utils.convert_huesat_st_to_matter(val) + return st_utils.clamp_value(math.floor((val * 0xFE) / 100.0 + 0.5), fields.CURRENT_HUESAT_ATTR_MIN, fields.CURRENT_HUESAT_ATTR_MAX) +end + +function utils.get_field_for_endpoint(device, field, endpoint) + return device:get_field(string.format("%s_%d", field, endpoint)) +end + +function utils.set_field_for_endpoint(device, field, endpoint, value, additional_params) + device:set_field(string.format("%s_%d", field, endpoint), value, additional_params) +end + +function utils.remove_field_index(device, field_name, index) + local new_table = device:get_field(field_name) + if type(new_table) == "table" then + new_table[index] = nil -- remove value associated with index from table + device:set_field(field_name, new_table) + end +end + +function utils.mired_to_kelvin(value, minOrMax) + if value == 0 then -- shouldn't happen, but has + value = 1 + log.warn(string.format("Received a color temperature of 0 mireds. Using a color temperature of 1 mired to avoid divide by zero")) + end + -- We divide inside the rounding and multiply outside of it because we expect these + -- bounds to be multiples of 100. For the maximum mired value (minimum K value), + -- add 1 before converting and round up to nearest hundreds. For the minimum mired + -- (maximum K value) value, subtract 1 before converting and round down to nearest + -- hundreds. Note that 1 is added/subtracted from the mired value in order to avoid + -- rounding errors from the conversion of Kelvin to mireds. + local kelvin_step_size = 100 + local rounding_value = 0.5 + if minOrMax == fields.COLOR_TEMP_MIN then + return st_utils.round(fields.MIRED_KELVIN_CONVERSION_CONSTANT / (kelvin_step_size * (value + 1)) + rounding_value) * kelvin_step_size + elseif minOrMax == fields.COLOR_TEMP_MAX then + return st_utils.round(fields.MIRED_KELVIN_CONVERSION_CONSTANT / (kelvin_step_size * (value - 1)) - rounding_value) * kelvin_step_size + else + log.warn_with({hub_logs = true}, "Attempted to convert temperature unit for an undefined value") + end +end + +function utils.get_product_override_field(device, override_key) + if device.manufacturer_info + and fields.vendor_overrides[device.manufacturer_info.vendor_id] + and fields.vendor_overrides[device.manufacturer_info.vendor_id][device.manufacturer_info.product_id] + then + return fields.vendor_overrides[device.manufacturer_info.vendor_id][device.manufacturer_info.product_id][override_key] + end +end + +function utils.check_field_name_updates(device) + for _, field in ipairs(fields.updated_fields) do + if device:get_field(field.current_field_name) then + if field.updated_field_name ~= nil then + device:set_field(field.updated_field_name, device:get_field(field.current_field_name), {persist = true}) + end + device:set_field(field.current_field_name, nil) + end + end +end + +function utils.check_switch_category_vendor_overrides(device) + for _, product_id in ipairs(fields.switch_category_vendor_overrides[device.manufacturer_info.vendor_id] or {}) do + if device.manufacturer_info.product_id == product_id then + return true + end + end +end + +--- device_type_supports_button_switch_combination helper function used to check +--- whether the device type for an endpoint is currently supported by a profile for +--- combination button/switch devices. +function utils.device_type_supports_button_switch_combination(device, endpoint_id) + if utils.get_product_override_field(device, "ignore_combo_switch_button") then + return false + end + local dimmable_eps = utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.LIGHT.DIMMABLE) + return utils.tbl_contains(dimmable_eps, endpoint_id) +end + +--- Some devices report multiple device types which are a subset of a superset +--- device type (Ex. Dimmable Light is a superset of On/Off Light). We should map +--- to the largest superset device type supported. +--- This can be done by matching to the device type with the highest ID +--- note: that superset device types have a higher ID than those of their subset +--- is heuristic and could therefore break in the future, were the spec expanded +function utils.find_max_subset_device_type(ep, device_type_set) + if ep.endpoint_id == 0 then return end -- EP-scoped device types not permitted on Root Node + local primary_dt_id = -1 + for _, dt in ipairs(ep.device_types) do + -- only device types in the subset should be considered. + if utils.tbl_contains(device_type_set, dt.device_type_id) then + primary_dt_id = math.max(primary_dt_id, dt.device_type_id) + end + end + return (primary_dt_id > 0) and primary_dt_id or nil +end + +--- Lights and Switches are Device Types that have Superset-style functionality +--- For all other device types, this function should be used to identify the primary device type +function utils.find_primary_device_type(ep_info) + for _, dt in ipairs(ep_info.device_types) do + if dt.device_type_id ~= fields.DEVICE_TYPE_ID.BRIDGED_NODE then + -- if this is not a bridged node, return the first device type seen + return dt.device_type_id + end + end +end + +--- find_default_endpoint is a helper function to handle situations where +--- device does not have endpoint ids in sequential order from 1 +function utils.find_default_endpoint(device) + -- Buttons should not be set on the main component for the Aqara Climate Sensor W100, + if utils.get_product_override_field(device, "is_climate_sensor_w100") then + return device.MATTER_DEFAULT_ENDPOINT + end + + local onoff_ep_ids = device:get_endpoints(clusters.OnOff.ID) + local momentary_switch_ep_ids = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}) + local fan_endpoint_ids = utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.FAN) + + local get_first_non_zero_endpoint = function(endpoints) + table.sort(endpoints) + for _,ep in ipairs(endpoints) do + if ep ~= 0 then -- 0 is the matter RootNode endpoint + return ep + end + end + return nil + end + + -- Return the first fan endpoint as the default endpoint if any is found + if #fan_endpoint_ids > 0 then + return get_first_non_zero_endpoint(fan_endpoint_ids) + end + + -- Return the first onoff endpoint as the default endpoint if no momentary switch endpoints are present + if #momentary_switch_ep_ids == 0 and #onoff_ep_ids > 0 then + return get_first_non_zero_endpoint(onoff_ep_ids) + end + + -- Return the first momentary switch endpoint as the default endpoint if no onoff endpoints are present + if #onoff_ep_ids == 0 and #momentary_switch_ep_ids > 0 then + return get_first_non_zero_endpoint(momentary_switch_ep_ids) + end + + -- If both onoff and momentary switch endpoints are present, check the device type on the first onoff + -- endpoint. If it is not a supported device type, return the first momentary switch endpoint as the + -- default endpoint. + if #onoff_ep_ids > 0 and #momentary_switch_ep_ids > 0 then + local default_endpoint_id = get_first_non_zero_endpoint(onoff_ep_ids) + if utils.device_type_supports_button_switch_combination(device, default_endpoint_id) then + return default_endpoint_id + else + device.log.warn("The main switch endpoint does not contain a supported device type for a component configuration with buttons") + return get_first_non_zero_endpoint(momentary_switch_ep_ids) + end + end + + device.log.warn(string.format("Did not find default endpoint, will use endpoint %d instead", device.MATTER_DEFAULT_ENDPOINT)) + return device.MATTER_DEFAULT_ENDPOINT +end + +function utils.component_to_endpoint(device, component) + local map = device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) or {} + if map[component] then + return map[component] + end + return utils.find_default_endpoint(device) +end + +--- An extension of the library function endpoint_to_component, used to support a mapping scheme +--- that optionally includes cluster and attribute ids so that multiple components can be mapped +--- to a single endpoint. +--- +--- @param device any a Matter device object +--- @param ep_info number|table either an ep_id or a table { endpoint_id, optional(cluster_id), optional(attribute_id) } +--- where cluster_id is required for an attribute_id to be handled. +--- @return string component +function utils.endpoint_to_component(device, ep_info) + if type(ep_info) == "number" then + ep_info = { endpoint_id = ep_info } + end + for component, map_info in pairs(device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) or {}) do + if type(map_info) == "number" and map_info == ep_info.endpoint_id then + return component + elseif type(map_info) == "table" and map_info.endpoint_id == ep_info.endpoint_id + and (not map_info.cluster_id or (map_info.cluster_id == ep_info.cluster_id + and (not map_info.attribute_ids or utils.tbl_contains(map_info.attribute_ids, ep_info.attribute_id)))) then + return component + end + end + return "main" +end + +--- An extension of the library function emit_event_for_endpoint, used to support devices with +--- multiple components mapped to the same endpoint. This is handled by extending the parameters to optionally +--- include a cluster id and attribute id for more specific routing +--- +--- @param device any a Matter device object +--- @param ep_info number|table endpoint_id or an ib (the ib data includes endpoint_id, cluster_id, and attribute_id fields) +--- @param event any a capability event object +function utils.emit_event_for_endpoint(device, ep_info, event) + if type(ep_info) == "number" then + ep_info = { endpoint_id = ep_info } + end + if device:get_field(fields.IS_PARENT_CHILD_DEVICE) then + local child = utils.find_child(device, ep_info.endpoint_id) + if child ~= nil then + child:emit_event(event) + return + end + end + local comp_id = utils.endpoint_to_component(device, ep_info) + local comp = device.profile.components[comp_id] + device:emit_component_event(comp, event) +end + +function utils.find_child(parent_device, ep_id) + local assigned_key = utils.get_field_for_endpoint(parent_device, fields.ASSIGNED_CHILD_KEY, ep_id) or ep_id + return parent_device:get_child_by_parent_assigned_key(string.format("%d", assigned_key)) +end + +function utils.get_endpoint_info(device, endpoint_id) + for _, ep in ipairs(device.endpoints) do + if ep.endpoint_id == endpoint_id then return ep end + end + return {} +end + +function utils.find_cluster_on_ep(ep, cluster_id, opts) + opts = opts or {} + local clus_has_features = function(cluster, checked_feature) + return (cluster.feature_map & checked_feature) == checked_feature + end + for _, cluster in ipairs(ep.clusters) do + if ((cluster.cluster_id == cluster_id) + and (opts.feature_bitmap == nil or clus_has_features(cluster, opts.feature_bitmap)) + and ((opts.cluster_type == nil and cluster.cluster_type == "SERVER" or cluster.cluster_type == "BOTH") + or (opts.cluster_type == cluster.cluster_type)) + or (cluster_id == nil)) then + return cluster + end + end +end + +-- Fallback handler for responses that dont have their own handler +function utils.matter_handler(driver, device, response_block) + device.log.info(string.format("Fallback handler for %s", response_block)) +end + +-- get a list of endpoints for a specified device type. +function utils.get_endpoints_by_device_type(device, device_type_id, opts) + opts = opts or {} + local dt_eps = {} + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == device_type_id then + if opts.with_info then + table.insert(dt_eps, ep) + else + table.insert(dt_eps, ep.endpoint_id) + end + break + end + end + end + return dt_eps +end + +--helper function to create list of multi press values +function utils.create_multi_press_values_list(size, supportsHeld) + local list = {"pushed", "double"} + if supportsHeld then table.insert(list, "held") end + -- add multi press values of 3 or greater to the list + for i=3, size do + table.insert(list, string.format("pushed_%dx", i)) + end + return list +end + +function utils.detect_bridge(device) + return #utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.AGGREGATOR) > 0 +end + +--- Generalizes the 'get_latest_state' function to be callable with extra endpoint information, described below, +--- without directly specifying the expected component. See the 'get_latest_state' definition for more +--- information about parameters and expected functionality otherwise. +--- +--- @param endpoint_info number|table an endpoint id or an ib (the ib data includes endpoint_id, cluster_id, and attribute_id fields) +function utils.get_latest_state_for_endpoint(device, endpoint_info, capability_id, attribute_id, default_value, default_state_table) + if type(endpoint_info) == "number" then + endpoint_info = { endpoint_id = endpoint_info } + end + + local component = device:endpoint_to_component(endpoint_info) + local state_device = utils.find_child(device, endpoint_info.endpoint_id) or device + return state_device:get_latest_state(component, capability_id, attribute_id, default_value, default_state_table) +end + +function utils.report_power_consumption_to_st_energy(device, endpoint_id, total_imported_energy_wh) + local current_time = os.time() + local last_time = device:get_field(fields.LAST_IMPORTED_REPORT_TIMESTAMP) or 0 + + -- Ensure that the previous report was sent at least 15 minutes ago + if fields.MINIMUM_ST_ENERGY_REPORT_INTERVAL >= (current_time - last_time) then + return + end + device:set_field(fields.LAST_IMPORTED_REPORT_TIMESTAMP, current_time, { persist = true }) + + local previous_imported_report = utils.get_latest_state_for_endpoint(device, endpoint_id, capabilities.powerConsumptionReport.ID, + capabilities.powerConsumptionReport.powerConsumption.NAME, { energy = total_imported_energy_wh }) -- default value if nil + -- Report the energy consumed during the time interval. The unit of these values should be 'Wh' + local epoch_to_iso8601 = function(time) return os.date("!%Y-%m-%dT%H:%M:%SZ", time) end -- Return an ISO-8061 timestamp from UTC + device:emit_event_for_endpoint(endpoint_id, capabilities.powerConsumptionReport.powerConsumption({ + start = epoch_to_iso8601(last_time), + ["end"] = epoch_to_iso8601(current_time - 1), + deltaEnergy = total_imported_energy_wh - previous_imported_report.energy, + energy = total_imported_energy_wh + })) +end + +--- sets fields for handling EPs with the Electrical Sensor device type +--- +--- @param device table a Matter device object +--- @param electrical_sensor_ep table an EP object that includes an Electrical Sensor device type +--- @param associated_endpoint_ids table EP IDs that are associated with the Electrical Sensor EP +--- @return boolean +function utils.set_fields_for_electrical_sensor_endpoint(device, electrical_sensor_ep, associated_endpoint_ids) + if #associated_endpoint_ids == 0 then + return false + else + local tags = "" + if utils.find_cluster_on_ep(electrical_sensor_ep, clusters.ElectricalPowerMeasurement.ID) then tags = tags.."-power" end + if utils.find_cluster_on_ep(electrical_sensor_ep, clusters.ElectricalEnergyMeasurement.ID) then tags = tags.."-energy-powerConsumption" end + -- note: using the lowest valued EP ID here is arbitrary (not spec defined) and is done to create internal consistency + -- Ex. for the NODE topology, electrical capabilities will then be associated with the default (aka lowest ID'd) OnOff EP + table.sort(associated_endpoint_ids) + local primary_associated_ep_id = associated_endpoint_ids[1] + -- map the required electrical tags for this electrical sensor EP with the first associated EP ID, used later during profling. + utils.set_field_for_endpoint(device, fields.ELECTRICAL_TAGS, primary_associated_ep_id, tags) + utils.set_field_for_endpoint(device, fields.ASSIGNED_CHILD_KEY, electrical_sensor_ep.endpoint_id, string.format("%d", primary_associated_ep_id), { persist = true }) + return true + end +end + +function utils.handle_electrical_sensor_info(device) + local electrical_sensor_eps = utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.ELECTRICAL_SENSOR, { with_info = true }) + if #electrical_sensor_eps == 0 then + -- no Electrical Sensor EPs are supported. Set profiling data to false and return + device:set_field(fields.profiling_data.POWER_TOPOLOGY, false, {persist=true}) + return + end + + -- check the feature map for the first (or only) Electrical Sensor EP + local endpoint_power_topology_cluster = utils.find_cluster_on_ep(electrical_sensor_eps[1], clusters.PowerTopology.ID) or {} + local endpoint_power_topology_feature_map = endpoint_power_topology_cluster.feature_map or 0 + if clusters.PowerTopology.are_features_supported(clusters.PowerTopology.types.Feature.SET_TOPOLOGY, endpoint_power_topology_feature_map) then + device:set_field(fields.ELECTRICAL_SENSOR_EPS, electrical_sensor_eps) -- assume any other stored EPs also have a SET topology + local available_eps_req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) -- SET read + for _, ep in ipairs(electrical_sensor_eps) do + available_eps_req:merge(clusters.PowerTopology.attributes.AvailableEndpoints:read(device, ep.endpoint_id)) + end + device:send(available_eps_req) + return + elseif clusters.PowerTopology.are_features_supported(clusters.PowerTopology.types.Feature.TREE_TOPOLOGY, endpoint_power_topology_feature_map) then + device:set_field(fields.ELECTRICAL_SENSOR_EPS, electrical_sensor_eps) -- assume any other stored EPs also have a TREE topology + local parts_list_req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) -- TREE read + for _, ep in ipairs(electrical_sensor_eps) do + parts_list_req:merge(clusters.Descriptor.attributes.PartsList:read(device, ep.endpoint_id)) + end + device:send(parts_list_req) + return + elseif clusters.PowerTopology.are_features_supported(clusters.PowerTopology.types.Feature.NODE_TOPOLOGY, endpoint_power_topology_feature_map) then + -- EP has a NODE topology, so there is only ONE Electrical Sensor EP + device:set_field(fields.profiling_data.POWER_TOPOLOGY, clusters.PowerTopology.types.Feature.NODE_TOPOLOGY, {persist=true}) + if utils.set_fields_for_electrical_sensor_endpoint(device, electrical_sensor_eps[1], device:get_endpoints(clusters.OnOff.ID)) == false then + device.log.warn("Electrical Sensor EP with NODE topology found, but no OnOff EPs exist. Electrical Sensor capabilities will not be exposed.") + end + return + end +end + +function utils.lazy_load(sub_driver_name) + if version.api >= 16 then + return MatterDriver.lazy_load_sub_driver_v2(sub_driver_name) + end +end + +function utils.lazy_load_if_possible(sub_driver_name) + if version.api >= 16 then + return MatterDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return MatterDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end +end + +--- helper for the switch subscribe override, which adds to a subscribed request for a checked device +--- +--- @param checked_device any a Matter device object, either a parent or child device, so not necessarily the same as device +--- @param subscribe_request table a subscribe request that will be appended to as needed for the device +--- @param capabilities_seen table a list of capabilities that have already been checked by previously handled devices +--- @param attributes_seen table a list of attributes that have already been checked +--- @param events_seen table a list of events that have already been checked +--- @param subscribed_attributes table key-value pairs mapping capability ids to subscribed attributes +--- @param subscribed_events table key-value pairs mapping capability ids to subscribed events +function utils.populate_subscribe_request_for_device(checked_device, subscribe_request, capabilities_seen, attributes_seen, events_seen, subscribed_attributes, subscribed_events) + for _, component in pairs(checked_device.st_store.profile.components) do + for _, capability in pairs(component.capabilities) do + if not capabilities_seen[capability.id] then + for _, attr in ipairs(subscribed_attributes[capability.id] or {}) do + local cluster_id = attr.cluster or attr._cluster.ID + local attr_id = attr.ID or attr.attribute + if not attributes_seen[cluster_id] or not attributes_seen[cluster_id][attr_id] then + local ib = im.InteractionInfoBlock(nil, cluster_id, attr_id) + subscribe_request:with_info_block(ib) + attributes_seen[cluster_id] = attributes_seen[cluster_id] or {} + attributes_seen[cluster_id][attr_id] = ib + end + end + for _, event in ipairs(subscribed_events[capability.id] or {}) do + local cluster_id = event.cluster or event._cluster.ID + local event_id = event.ID or event.event + if not events_seen[cluster_id] or not events_seen[cluster_id][event_id] then + local ib = im.InteractionInfoBlock(nil, cluster_id, nil, event_id) + subscribe_request:with_info_block(ib) + events_seen[cluster_id] = events_seen[cluster_id] or {} + events_seen[cluster_id][event_id] = ib + end + end + capabilities_seen[capability.id] = true -- only loop through any capability once + end + end + end +end + +--- create and send a subscription request by checking all devices, accounting for both parent and child devices +--- +--- @param device any a Matter device object +function utils.subscribe(device) + local subscribe_request = im.InteractionRequest(im.InteractionRequest.RequestType.SUBSCRIBE, {}) + local devices_seen, capabilities_seen, attributes_seen, events_seen = {}, {}, {}, {} + + for _, endpoint_info in ipairs(device.endpoints) do + local checked_device = utils.find_child(device, endpoint_info.endpoint_id) or device + if not devices_seen[checked_device.id] then + utils.populate_subscribe_request_for_device(checked_device, subscribe_request, capabilities_seen, attributes_seen, events_seen, + device.driver.subscribed_attributes, device.driver.subscribed_events + ) + devices_seen[checked_device.id] = true -- only loop through any device once + end + end + -- The refresh capability command handler in the lua libs uses this key to determine which attributes to read. Note + -- that only attributes_seen needs to be saved here, and not events_seen, since the refresh handler only checks + -- attributes and not events. + device:set_field(fields.SUBSCRIBED_ATTRIBUTES_KEY, attributes_seen) + + if #subscribe_request.info_blocks > 0 then + device:send(subscribe_request) + end +end + +return utils diff --git a/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua b/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua index d2e99b2331..6371c6be44 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright © 2024 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" @@ -25,6 +14,7 @@ local button_attr = capabilities.button.button local aqara_mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("3-button-battery-temperature-humidity.yml"), manufacturer_info = {vendor_id = 0x115F, product_id = 0x2004, product_name = "Aqara Climate Sensor W100"}, + matter_version = {hardware = 1, software = 1}, label = "Climate Sensor W100", device_id = "00000000-1111-2222-3333-000000000001", endpoints = { diff --git a/drivers/SmartThings/matter-switch/src/test/test_aqara_cube.lua b/drivers/SmartThings/matter-switch/src/test/test_aqara_cube.lua index e641cb88ca..3c377255ab 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_aqara_cube.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_aqara_cube.lua @@ -1,3 +1,6 @@ +-- Copyright © 2024 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" test.add_package_capability("cubeAction.yml") test.add_package_capability("cubeFace.yml") diff --git a/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua b/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua index 548ce67890..35f4cd9ce7 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright © 2024 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" @@ -27,10 +16,6 @@ if version.api < 11 then clusters.PowerTopology = require "embedded_clusters.PowerTopology" end -if version.api < 16 then - clusters.Descriptor = require "embedded_clusters.Descriptor" -end - local aqara_parent_ep = 4 local aqara_child1_ep = 1 local aqara_child2_ep = 2 @@ -38,6 +23,7 @@ local aqara_child2_ep = 2 local aqara_mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("4-button.yml"), manufacturer_info = {vendor_id = 0x115F, product_id = 0x1009, product_name = "Aqara Light Switch H2"}, + matter_version = {hardware = 1, software = 1}, label = "Aqara Light Switch", device_id = "00000000-1111-2222-3333-000000000001", endpoints = { @@ -46,7 +32,8 @@ local aqara_mock_device = test.mock_device.build_test_matter_device({ clusters = { {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, {cluster_id = clusters.ElectricalPowerMeasurement.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 2 }, - {cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 5 } + {cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 5 }, + {cluster_id = clusters.PowerTopology.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 1 } -- NODE_TOPOLOGY }, device_types = { {device_type_id = 0x0016, device_type_revision = 1}, -- RootNode @@ -134,6 +121,8 @@ local cumulative_report_val_19 = { end_timestamp = 0, start_systime = 0, end_systime = 0, + apparent_energy = 0, + reactive_energy = 0 } local cumulative_report_val_29 = { @@ -142,6 +131,8 @@ local cumulative_report_val_29 = { end_timestamp = 0, start_systime = 0, end_systime = 0, + apparent_energy = 0, + reactive_energy = 0 } local cumulative_report_val_39 = { @@ -150,6 +141,8 @@ local cumulative_report_val_39 = { end_timestamp = 0, start_systime = 0, end_systime = 0, + apparent_energy = 0, + reactive_energy = 0 } local function configure_buttons() @@ -282,10 +275,8 @@ test.register_coroutine_test( function() test.socket.matter:__queue_receive( { - -- don't use "aqara_mock_children[aqara_child1_ep].id," - -- because energy management is at the root endpoint. aqara_mock_device.id, - clusters.ElectricalPowerMeasurement.attributes.ActivePower:build_test_report_data(aqara_mock_device, 1, 17000) + clusters.ElectricalPowerMeasurement.attributes.ActivePower:build_test_report_data(aqara_mock_device, 0, 17000) } ) @@ -299,7 +290,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive( { aqara_mock_device.id, - clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(aqara_mock_device, 1, cumulative_report_val_19) + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(aqara_mock_device, 0, cumulative_report_val_19) } ) @@ -319,7 +310,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive( { aqara_mock_device.id, - clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(aqara_mock_device, 1, cumulative_report_val_29) + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(aqara_mock_device, 0, cumulative_report_val_29) } ) @@ -336,7 +327,7 @@ test.register_coroutine_test( { aqara_mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data( - aqara_mock_device, 1, cumulative_report_val_39 + aqara_mock_device, 0, cumulative_report_val_39 ) } ) @@ -349,7 +340,7 @@ test.register_coroutine_test( aqara_mock_children[aqara_child1_ep]:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ start = "1970-01-01T00:15:01Z", ["end"] = "1970-01-01T00:40:00Z", - deltaEnergy = 0.0, + deltaEnergy = 20.0, energy = 39.0 })) ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua new file mode 100644 index 0000000000..8e3ad613da --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua @@ -0,0 +1,738 @@ +-- Copyright © 2024 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local t_utils = require "integration_test.utils" +local uint32 = require "st.matter.data_types.Uint32" +local version = require "version" +local st_utils = require "st.utils" + +if version.api < 11 then + clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement" + clusters.ElectricalPowerMeasurement = require "embedded_clusters.ElectricalPowerMeasurement" + clusters.PowerTopology = require "embedded_clusters.PowerTopology" +end + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("plug-level-power-energy-powerConsumption.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.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 14, }, + { cluster_id = clusters.ElectricalPowerMeasurement.ID, cluster_type = "SERVER", feature_map = 0, }, + { cluster_id = clusters.PowerTopology.ID, cluster_type = "SERVER", feature_map = 4, }, -- SET_TOPOLOGY + }, + device_types = { + { device_type_id = 0x0510, device_type_revision = 1 }, -- Electrical Sensor + } + }, + { + endpoint_id = 2, + clusters = { + { cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0, }, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2} + }, + device_types = { + { device_type_id = 0x010B, device_type_revision = 1 }, -- Dimmable Plug In Unit + } + }, + { + endpoint_id = 3, + clusters = { + { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 14, }, + { cluster_id = clusters.PowerTopology.ID, cluster_type = "SERVER", feature_map = 4, }, -- SET_TOPOLOGY + }, + device_types = { + { device_type_id = 0x0510, device_type_revision = 1 }, -- Electrical Sensor + } + }, + { + endpoint_id = 4, + clusters = { + { cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0, }, + { cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2}, + }, + device_types = { + { device_type_id = 0x010B, device_type_revision = 1 }, -- Dimmable Plug In Unit + } + }, + }, +}) + + +local mock_device_periodic = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("plug-energy-powerConsumption.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.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0, }, + { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 10, }, + { cluster_id = clusters.PowerTopology.ID, cluster_type = "SERVER", feature_map = 4, } -- SET_TOPOLOGY + }, + device_types = { + { device_type_id = 0x010A, device_type_revision = 1 }, -- OnOff Plug + { device_type_id = 0x0510, device_type_revision = 1 }, -- Electrical Sensor + } + }, + }, +}) + +local subscribed_attributes_periodic = { + clusters.OnOff.attributes.OnOff, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, +} +local subscribed_attributes = { + clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel, + clusters.ElectricalPowerMeasurement.attributes.ActivePower, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, +} + +local cumulative_report_val_19 = { + energy = 19000, + start_timestamp = 0, + end_timestamp = 0, + start_systime = 0, + end_systime = 0, + apparent_energy = 0, + reactive_energy = 0 +} + +local cumulative_report_val_29 = { + energy = 29000, + start_timestamp = 0, + end_timestamp = 0, + start_systime = 0, + end_systime = 0, + apparent_energy = 0, + reactive_energy = 0 +} + +local cumulative_report_val_39 = { + energy = 39000, + start_timestamp = 0, + end_timestamp = 0, + start_systime = 0, + end_systime = 0, + apparent_energy = 0, + reactive_energy = 0 +} + +local periodic_report_val_23 = { + energy = 23000, + start_timestamp = 0, + end_timestamp = 0, + start_systime = 0, + end_systime = 0, + apparent_energy = 0, + reactive_energy = 0 +} + +local mock_child = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("plug-level-energy-powerConsumption.yml"), + device_network_id = string.format("%s:%d", mock_device.id, 4), + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%d", 4) +}) + +local function test_init() + test.mock_device.add_test_device(mock_device) + test.mock_device.add_test_device(mock_child) + + local subscribe_request = subscribed_attributes[1]:subscribe(mock_device) + for i, cluster in ipairs(subscribed_attributes) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device)) + end + end + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + local read_req = clusters.PowerTopology.attributes.AvailableEndpoints:read(mock_device.id, 1) + read_req:merge(clusters.PowerTopology.attributes.AvailableEndpoints:read(mock_device.id, 3)) + test.socket.matter:__expect_send({ mock_device.id, read_req }) + test.socket.matter:__expect_send({ mock_device.id, subscribe_request }) + test.socket.matter:__expect_send({ mock_device.id, subscribe_request }) +end +test.set_test_init_function(test_init) + +local function test_init_periodic() + test.mock_device.add_test_device(mock_device_periodic) + local subscribe_request = subscribed_attributes_periodic[1]:subscribe(mock_device_periodic) + for i, cluster in ipairs(subscribed_attributes_periodic) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device_periodic)) + end + end + test.socket.device_lifecycle:__queue_receive({ mock_device_periodic.id, "added" }) + local read_req = clusters.PowerTopology.attributes.AvailableEndpoints:read(mock_device_periodic.id, 1) + test.socket.matter:__expect_send({ mock_device_periodic.id, read_req }) + test.socket.matter:__expect_send({ mock_device_periodic.id, subscribe_request }) + test.socket.device_lifecycle:__queue_receive({ mock_device_periodic.id, "init" }) + test.socket.matter:__expect_send({ mock_device_periodic.id, subscribe_request }) + test.socket.matter:__expect_send({ mock_device_periodic.id, subscribe_request }) +end + +test.register_message_test( + "On command should send the appropriate commands", + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "on" } + } + }, + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "switch", component = "main", command = "on", args = { } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.On(mock_device, 2) + } + } + } +) + +test.register_message_test( + "Off command should send the appropriate commands", + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "off" } + } + }, + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "switch", component = "main", command = "off", args = { } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.Off(mock_device, 2) + } + } + } +) + +test.register_message_test( + "Active power measurement should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ElectricalPowerMeasurement.server.attributes.ActivePower:build_test_report_data(mock_device, 1, 17000) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerMeter.power({value = 17.0, unit="W"})) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "powerMeter", capability_attr_id = "power" } + } + } + } +) + +test.register_coroutine_test( + "Cumulative Energy measurement should generate correct messages", + function() + + test.mock_time.advance_time(901) -- move time 15 minutes past 0 (this can be assumed to be true in practice in all cases) + + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:build_test_report_data( + mock_device, 1, cumulative_report_val_19 + ) + } + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 19.0, unit = "Wh" })) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:15:00Z", + deltaEnergy = 0.0, + energy = 19.0 + })) + ) + + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:build_test_report_data( + mock_device, 1, cumulative_report_val_29 + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 29.0, unit = "Wh" })) + ) + + test.wait_for_events() + test.mock_time.advance_time(1500) + + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:build_test_report_data( + mock_device, 1, cumulative_report_val_39 + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 39.0, unit = "Wh" })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:15:01Z", + ["end"] = "1970-01-01T00:40:00Z", + deltaEnergy = 20.0, + energy = 39.0 + })) + ) + end +) + +test.register_coroutine_test( + "Periodic Energy as subordinate to Cumulative Energy measurement should not generate any messages", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data( + mock_device, 1, periodic_report_val_23 + ) + } + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data( + mock_device, 1, periodic_report_val_23 + ) + } + ) + end +) + +test.register_coroutine_test( + "Periodic Energy measurement should generate correct messages", + function() + test.mock_time.advance_time(901) -- move time 15 minutes past 0 (this can be assumed to be true in practice in all cases) + test.socket.matter:__queue_receive( + { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data( + mock_device_periodic, 1, periodic_report_val_23 + ) + } + ) + test.socket.capability:__expect_send( + mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 23.0, unit="Wh"})) + ) + test.socket.capability:__expect_send( + mock_device_periodic:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:15:00Z", + deltaEnergy = 0.0, + energy = 23.0 + })) + ) + test.socket.matter:__queue_receive( + { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data( + mock_device_periodic, 1, periodic_report_val_23 + ) + } + ) + test.socket.capability:__expect_send( + mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 46.0, unit="Wh"})) + ) + test.wait_for_events() + test.mock_time.advance_time(2000) + test.socket.matter:__queue_receive( + { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data( + mock_device_periodic, 1, periodic_report_val_23 + ) + } + ) + test.socket.capability:__expect_send( + mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 69.0, unit="Wh"})) + ) + test.socket.capability:__expect_send( + mock_device_periodic:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:15:01Z", + ["end"] = "1970-01-01T00:48:20Z", + deltaEnergy = 46.0, + energy = 69.0 + })) + ) + end, + { test_init = test_init_periodic } +) + +test.register_coroutine_test( + "Test profile change on init for Electrical Sensor device type", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, 2, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, 4, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + test.socket.matter:__queue_receive({ mock_device.id, clusters.PowerTopology.attributes.AvailableEndpoints:build_test_report_data(mock_device, 1, {uint32(2)})}) + test.socket.matter:__queue_receive({ mock_device.id, clusters.PowerTopology.attributes.AvailableEndpoints:build_test_report_data(mock_device, 3, {uint32(4)})}) + mock_device:expect_metadata_update({ profile = "plug-level-power-energy-powerConsumption" }) + mock_device:expect_device_create({ + type = "EDGE_CHILD", + label = "nil 2", + profile = "plug-level-energy-powerConsumption", + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%d", 4) + }) + end, + { test_init = test_init } +) + +test.register_coroutine_test( + "Test profile change on init for only Periodic Electrical Sensor device type", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_periodic.id, "doConfigure" }) + mock_device_periodic:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + test.socket.matter:__queue_receive({ mock_device_periodic.id, clusters.PowerTopology.attributes.AvailableEndpoints:build_test_report_data(mock_device_periodic, 1, {uint32(1)})}) + mock_device_periodic:expect_metadata_update({ profile = "plug-energy-powerConsumption" }) + end, + { test_init = test_init_periodic } +) + +test.register_coroutine_test( + "Test resetEnergyMeter command on parent and child for CumulativeEnergyImported", + function() + + test.mock_time.advance_time(901) -- move time 15 minutes past 0 (this can be assumed to be true in practice in all cases) + + -- this block needs to run to set the requisite fields. It is tested on its own elsewhere + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, 2, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, 4, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + test.socket.matter:__queue_receive({ mock_device.id, clusters.PowerTopology.attributes.AvailableEndpoints:build_test_report_data(mock_device, 1, {uint32(2)})}) + test.socket.matter:__queue_receive({ mock_device.id, clusters.PowerTopology.attributes.AvailableEndpoints:build_test_report_data(mock_device, 3, {uint32(4)})}) + mock_device:expect_metadata_update({ profile = "plug-level-power-energy-powerConsumption" }) + mock_device:expect_device_create({ + type = "EDGE_CHILD", + label = "nil 2", + profile = "plug-level-energy-powerConsumption", + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%d", 4) + }) + -- end of block + + -- Initial Parent Energy Report + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:build_test_report_data( + mock_device, 1, cumulative_report_val_19 + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 19.0, unit = "Wh" })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:15:00Z", + deltaEnergy = 0.0, + energy = 19.0 + })) + ) + + -- Initial Child Energy Report + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:build_test_report_data( + mock_device, 3, cumulative_report_val_19 + ) + } + ) + test.socket.capability:__expect_send( + mock_child:generate_test_message("main", capabilities.energyMeter.energy({ value = 19.0, unit = "Wh" })) + ) + -- no powerConsumptionReport will be emitted now, since it has not been 15 minutes since the previous report (even though it was the parent). + + + test.wait_for_events() + test.mock_time.advance_time(1500) + + + -- Parent call to resetEnergyMeter + test.socket.capability:__queue_receive({mock_device.id, { capability = "energyMeter", component = "main", command = "resetEnergyMeter", args = {}}}) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) + ) + -- Child call to resetEnergyMeter + test.socket.capability:__queue_receive({mock_child.id, { capability = "energyMeter", component = "main", command = "resetEnergyMeter", args = {}}}) + test.socket.capability:__expect_send( + mock_child:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) + ) + + test.wait_for_events() + + -- Second Child Energy Report + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:build_test_report_data( + mock_device, 3, cumulative_report_val_39 + ) + } + ) + test.socket.capability:__expect_send( + mock_child:generate_test_message("main", capabilities.energyMeter.energy({ value = 20.0, unit = "Wh" })) + ) + test.socket.capability:__expect_send( + mock_child:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:15:01Z", + ["end"] = "1970-01-01T00:40:00Z", + deltaEnergy = 0.0, + energy = 20.0 + })) + ) + + -- Second Parent Energy Report + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:build_test_report_data( + mock_device, 1, cumulative_report_val_39 + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 20.0, unit = "Wh" })) + ) + -- no powerConsumptionReport will be emitted now, since it has not been 15 minutes since the previous report (even though it was the child). + end, + { test_init = test_init } +) + +test.register_coroutine_test( + "Test resetEnergyMeter command on device for PeriodicEnergyImported", + function() + test.mock_time.advance_time(901) -- move time 15 minutes past 0 (this can be assumed to be true in practice in all cases) + test.socket.matter:__queue_receive( + { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data( + mock_device_periodic, 1, periodic_report_val_23 + ) + } + ) + test.socket.capability:__expect_send( + mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 23.0, unit="Wh"})) + ) + test.socket.capability:__expect_send( + mock_device_periodic:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:15:00Z", + deltaEnergy = 0.0, + energy = 23.0 + })) + ) + test.socket.matter:__queue_receive( + { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data( + mock_device_periodic, 1, periodic_report_val_23 + ) + } + ) + test.socket.capability:__expect_send( + mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 46.0, unit="Wh"})) + ) + + test.wait_for_events() + test.mock_time.advance_time(2000) + + test.socket.capability:__queue_receive({mock_device_periodic.id, { capability = "energyMeter", component = "main", command = "resetEnergyMeter", args = {}}}) + test.socket.capability:__expect_send( + mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) + ) + + test.wait_for_events() + + test.socket.matter:__queue_receive( + { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data( + mock_device_periodic, 1, cumulative_report_val_19 + ) + } + ) + test.socket.capability:__expect_send( + mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 19.0, unit="Wh"})) + ) + test.socket.capability:__expect_send( + mock_device_periodic:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:15:01Z", + ["end"] = "1970-01-01T00:48:20Z", + deltaEnergy = -4.0, + energy = 19.0 + })) + ) + end, + { test_init = test_init_periodic } +) + +test.register_message_test( + "Set level command should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "switchLevel", component = "main", command = "setLevel", args = {20,20} } + } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "switchLevel", capability_cmd_id = "setLevel" } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.LevelControl.server.commands.MoveToLevelWithOnOff(mock_device, 2, st_utils.round(20/100.0 * 254), 20, 0 ,0) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LevelControl.server.commands.MoveToLevelWithOnOff:build_test_command_response(mock_device, 2) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LevelControl.attributes.CurrentLevel:build_test_report_data(mock_device, 2, 50) + } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "switchLevel", capability_attr_id = "level" } + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.switchLevel.level(20)) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, 2, true) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua similarity index 63% rename from drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua rename to drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua index d981af3110..b548bb819b 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua @@ -1,26 +1,22 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local t_utils = require "integration_test.utils" +local uint32 = require "st.matter.data_types.Uint32" local version = require "version" +local st_utils = require "st.utils" if version.api < 11 then clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement" clusters.ElectricalPowerMeasurement = require "embedded_clusters.ElectricalPowerMeasurement" + clusters.PowerTopology = require "embedded_clusters.PowerTopology" +end + +if version.api < 16 then + clusters.Descriptor = require "embedded_clusters.Descriptor" end local mock_device = test.mock_device.build_test_matter_device({ @@ -44,6 +40,7 @@ local mock_device = test.mock_device.build_test_matter_device({ clusters = { { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 14, }, { cluster_id = clusters.ElectricalPowerMeasurement.ID, cluster_type = "SERVER", feature_map = 0, }, + { cluster_id = clusters.PowerTopology.ID, cluster_type = "SERVER", feature_map = 2, }, -- TREE_TOPOLOGY }, device_types = { { device_type_id = 0x0510, device_type_revision = 1 }, -- Electrical Sensor @@ -56,45 +53,32 @@ local mock_device = test.mock_device.build_test_matter_device({ {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2} }, device_types = { - { device_type_id = 0x010A, device_type_revision = 1 } -- OnOff Plug + { device_type_id = 0x010B, device_type_revision = 1 }, -- OnOff Dimmable Plug } - } - }, -}) - - -local mock_device_periodic = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("plug-energy-powerConsumption.yml"), - manufacturer_info = { - vendor_id = 0x0000, - product_id = 0x0000, - }, - endpoints = { + }, { - endpoint_id = 0, + endpoint_id = 3, clusters = { - { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 14, }, + { cluster_id = clusters.PowerTopology.ID, cluster_type = "SERVER", feature_map = 2, }, -- TREE_TOPOLOGY }, device_types = { - { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode + { device_type_id = 0x0510, device_type_revision = 1 }, -- Electrical Sensor } }, { - endpoint_id = 1, + endpoint_id = 4, clusters = { - { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 10, }, + { cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0, }, + { cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2}, }, device_types = { - { device_type_id = 0x0510, device_type_revision = 1 } -- Electrical Sensor + { device_type_id = 0x010B, device_type_revision = 1 }, -- OnOff Dimmable Plug } }, }, }) -local subscribed_attributes_periodic = { - clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, - clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, -} local subscribed_attributes = { clusters.OnOff.attributes.OnOff, clusters.LevelControl.attributes.CurrentLevel, @@ -111,6 +95,8 @@ local cumulative_report_val_19 = { end_timestamp = 0, start_systime = 0, end_systime = 0, + apparent_energy = 0, + reactive_energy = 0 } local cumulative_report_val_29 = { @@ -119,6 +105,8 @@ local cumulative_report_val_29 = { end_timestamp = 0, start_systime = 0, end_systime = 0, + apparent_energy = 0, + reactive_energy = 0 } local cumulative_report_val_39 = { @@ -127,43 +115,27 @@ local cumulative_report_val_39 = { end_timestamp = 0, start_systime = 0, end_systime = 0, -} - -local periodic_report_val_23 = { - energy = 23000, - start_timestamp = 0, - end_timestamp = 0, - start_systime = 0, - end_systime = 0, + apparent_energy = 0, + reactive_energy = 0 } local function test_init() + test.mock_device.add_test_device(mock_device) local subscribe_request = subscribed_attributes[1]:subscribe(mock_device) for i, cluster in ipairs(subscribed_attributes) do if i > 1 then subscribe_request:merge(cluster:subscribe(mock_device)) end end + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + local read_req = clusters.Descriptor.attributes.PartsList:read(mock_device.id, 1) + read_req:merge(clusters.Descriptor.attributes.PartsList:read(mock_device.id, 3)) + test.socket.matter:__expect_send({ mock_device.id, read_req }) + test.socket.matter:__expect_send({ mock_device.id, subscribe_request }) test.socket.matter:__expect_send({ mock_device.id, subscribe_request }) - test.mock_device.add_test_device(mock_device) end test.set_test_init_function(test_init) -local function test_init_periodic() - test.mock_device.add_test_device(mock_device_periodic) - local subscribe_request = subscribed_attributes_periodic[1]:subscribe(mock_device_periodic) - for i, cluster in ipairs(subscribed_attributes_periodic) do - if i > 1 then - subscribe_request:merge(cluster:subscribe(mock_device_periodic)) - end - end - test.socket.matter:__expect_send({ mock_device_periodic.id, subscribe_request }) - test.socket.device_lifecycle:__queue_receive({ mock_device_periodic.id, "added" }) - test.socket.matter:__expect_send({ mock_device_periodic.id, subscribe_request }) - test.socket.device_lifecycle:__queue_receive({ mock_device_periodic.id, "init" }) - test.socket.matter:__expect_send({ mock_device_periodic.id, subscribe_request }) -end - test.register_message_test( "On command should send the appropriate commands", { @@ -316,107 +288,29 @@ test.register_coroutine_test( end ) -test.register_message_test( - "Periodic Energy as subordinate to Cumulative Energy measurement should not generate any messages", - { - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data(mock_device, 1, periodic_report_val_23) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data(mock_device, 1, periodic_report_val_23) - } - }, - } -) - -test.register_coroutine_test( - "Periodic Energy measurement should generate correct messages", - function() - test.mock_time.advance_time(901) -- move time 15 minutes past 0 (this can be assumed to be true in practice in all cases) - test.socket.matter:__queue_receive( - { - mock_device_periodic.id, - clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data( - mock_device_periodic, 1, periodic_report_val_23 - ) - } - ) - test.socket.capability:__expect_send( - mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 23.0, unit="Wh"})) - ) - test.socket.capability:__expect_send( - mock_device_periodic:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ - start = "1970-01-01T00:00:00Z", - ["end"] = "1970-01-01T00:15:00Z", - deltaEnergy = 0.0, - energy = 23.0 - })) - ) - test.socket.matter:__queue_receive( - { - mock_device_periodic.id, - clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data( - mock_device_periodic, 1, periodic_report_val_23 - ) - } - ) - test.socket.capability:__expect_send( - mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 46.0, unit="Wh"})) - ) - test.wait_for_events() - test.mock_time.advance_time(2000) - test.socket.matter:__queue_receive( - { - mock_device_periodic.id, - clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data( - mock_device_periodic, 1, periodic_report_val_23 - ) - } - ) - test.socket.capability:__expect_send( - mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 69.0, unit="Wh"})) - ) - test.socket.capability:__expect_send( - mock_device_periodic:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ - start = "1970-01-01T00:15:01Z", - ["end"] = "1970-01-01T00:48:20Z", - deltaEnergy = 46.0, - energy = 69.0 - })) - ) - end, - { test_init = test_init_periodic } -) - test.register_coroutine_test( "Test profile change on init for Electrical Sensor device type", function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) - mock_device:expect_metadata_update({ profile = "plug-level-power-energy-powerConsumption" }) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, 2, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, 4, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + test.socket.matter:__queue_receive({ mock_device.id, clusters.Descriptor.attributes.PartsList:build_test_report_data(mock_device, 1, {uint32(2)})}) + test.socket.matter:__queue_receive({ mock_device.id, clusters.Descriptor.attributes.PartsList:build_test_report_data(mock_device, 3, {uint32(4)})}) + mock_device:expect_metadata_update({ profile = "plug-level-power-energy-powerConsumption" }) + mock_device:expect_device_create({ + type = "EDGE_CHILD", + label = "nil 2", + profile = "plug-level-energy-powerConsumption", + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%d", 4) + }) end, { test_init = test_init } ) -test.register_coroutine_test( - "Test profile change on init for only Periodic Electrical Sensor device type", - function() - test.socket.device_lifecycle:__queue_receive({ mock_device_periodic.id, "doConfigure" }) - mock_device_periodic:expect_metadata_update({ profile = "plug-energy-powerConsumption" }) - mock_device_periodic:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end, - { test_init = test_init_periodic } -) - test.register_message_test( "Set level command should send the appropriate commands", { @@ -441,7 +335,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.LevelControl.server.commands.MoveToLevelWithOnOff(mock_device, 2, math.floor(20/100.0 * 254), 20, 0 ,0) + clusters.LevelControl.server.commands.MoveToLevelWithOnOff(mock_device, 2, st_utils.round(20/100.0 * 254), 20, 0 ,0) } }, { diff --git a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua index 5ac4cea964..a189b1e5fc 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -1,21 +1,11 @@ --- 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. +-- Copyright © 2023 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local data_types = require "st.matter.data_types" +local fields = require "switch_utils.fields" local clusters = require "st.matter.clusters" local cluster_base = require "st.matter.cluster_base" @@ -64,6 +54,89 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) +local mock_device_electrical_sensor = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("plug-energy-powerConsumption.yml"), + manufacturer_info = { + vendor_id = 0x130A, + product_id = 0x0050, + }, + 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.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0, --u32 bitmap + }, + { + cluster_id = PRIVATE_CLUSTER_ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0, --u32 bitmap + } + }, + device_types = { + { device_type_id = 0x010A, device_type_revision = 1 } -- On/Off Plug + } + }, + { + endpoint_id = 2, + clusters = { + { + cluster_id = clusters.ElectricalEnergyMeasurement.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY, + }, + { + cluster_id = clusters.PowerTopology.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = clusters.PowerTopology.types.Feature.NODE_TOPOLOGY, + } + }, + device_types = { + { device_type_id = fields.DEVICE_TYPE_ID.ELECTRICAL_SENSOR, device_type_revision = 1 } + } + } + } +}) + +local function test_init_electrical_sensor() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device_electrical_sensor) + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + } + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_electrical_sensor) + for i, clus in ipairs(cluster_subscribe_list) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_electrical_sensor)) end + end + + test.socket.device_lifecycle:__queue_receive({ mock_device_electrical_sensor.id, "added" }) + test.socket.matter:__expect_send({mock_device_electrical_sensor.id, subscribe_request}) + + test.socket.device_lifecycle:__queue_receive({ mock_device_electrical_sensor.id, "init" }) + test.socket.matter:__expect_send({mock_device_electrical_sensor.id, subscribe_request}) + + test.socket.device_lifecycle:__queue_receive({ mock_device_electrical_sensor.id, "doConfigure" }) + mock_device_electrical_sensor:expect_metadata_update({ profile = "plug-energy-powerConsumption" }) + mock_device_electrical_sensor:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + local function test_init() local cluster_subscribe_list = { clusters.OnOff.attributes.OnOff, @@ -385,4 +458,94 @@ test.register_coroutine_test( } ) +local cumulative_report_val_19 = { + energy = 19000, + start_timestamp = 0, + end_timestamp = 0, + start_systime = 0, + end_systime = 0, + apparent_energy = 0, + reactive_energy = 0 +} + +local cumulative_report_val_29 = { + energy = 29000, + start_timestamp = 0, + end_timestamp = 0, + start_systime = 0, + end_systime = 0, + apparent_energy = 0, + reactive_energy = 0 +} + +local cumulative_report_val_39 = { + energy = 39000, + start_timestamp = 0, + end_timestamp = 0, + start_systime = 0, + end_systime = 0, + apparent_energy = 0, + reactive_energy = 0 +} + +test.register_coroutine_test( + "Cumulative Energy measurement should generate correct messages", + function() + local mock_device = mock_device_electrical_sensor + + test.mock_time.advance_time(901) -- move time 15 minutes past 0 (this can be assumed to be true in practice in all cases) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:build_test_report_data( + mock_device, 1, cumulative_report_val_19 + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 19.0, unit = "Wh" })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:15:00Z", + deltaEnergy = 0.0, + energy = 19.0 + })) + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:build_test_report_data( + mock_device, 1, cumulative_report_val_29 + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 29.0, unit = "Wh" })) + ) + test.wait_for_events() + test.mock_time.advance_time(1500) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:build_test_report_data( + mock_device, 1, cumulative_report_val_39 + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 39.0, unit = "Wh" })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:15:01Z", + ["end"] = "1970-01-01T00:40:00Z", + deltaEnergy = 20.0, + energy = 39.0 + })) + ) + end, { test_init = test_init_electrical_sensor } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua new file mode 100644 index 0000000000..c560386e37 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua @@ -0,0 +1,224 @@ +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" + +local mock_ikea_scroll = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("ikea-scroll.yml"), + manufacturer_info = {vendor_id = 0x117C, product_id = 0x8000, product_name = "Ikea Scroll"}, + label = "Ikea Scroll", + 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.Switch.ID, + feature_map = + clusters.Switch.types.Feature.MOMENTARY_SWITCH | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS, + cluster_type = "SERVER" + },}, + device_types = {{device_type_id = 0x000F, device_type_revision = 1}} -- GENERIC SWITCH + }, + { + endpoint_id = 2, + clusters = {{ + cluster_id = clusters.Switch.ID, + feature_map = + clusters.Switch.types.Feature.MOMENTARY_SWITCH | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS, + cluster_type = "SERVER" + },}, + device_types = {{device_type_id = 0x000F, device_type_revision = 1}} -- GENERIC SWITCH + }, + { + endpoint_id = 3, + clusters = {{ + cluster_id = clusters.Switch.ID, + feature_map = + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_LONG_PRESS, + cluster_type = "SERVER"}, + }, + device_types = {{device_type_id = 0x000F, device_type_revision = 1}} -- GENERIC SWITCH + }, + { + endpoint_id = 4, + clusters = {{ + cluster_id = clusters.Switch.ID, + feature_map = + clusters.Switch.types.Feature.MOMENTARY_SWITCH | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS, + cluster_type = "SERVER" + },}, + device_types = {{device_type_id = 0x000F, device_type_revision = 1}} -- GENERIC SWITCH + }, + { + endpoint_id = 5, + clusters = {{ + cluster_id = clusters.Switch.ID, + feature_map = + clusters.Switch.types.Feature.MOMENTARY_SWITCH | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS, + cluster_type = "SERVER" + },}, + device_types = {{device_type_id = 0x000F, device_type_revision = 1}} -- GENERIC SWITCH + }, + { + endpoint_id = 6, + clusters = {{ + cluster_id = clusters.Switch.ID, + feature_map = + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_LONG_PRESS, + cluster_type = "SERVER"}, + }, + device_types = {{device_type_id = 0x000F, device_type_revision = 1}} -- GENERIC SWITCH + }, + { + endpoint_id = 7, + clusters = {{ + cluster_id = clusters.Switch.ID, + feature_map = + clusters.Switch.types.Feature.MOMENTARY_SWITCH | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS, + cluster_type = "SERVER" + },}, + device_types = {{device_type_id = 0x000F, device_type_revision = 1}} -- GENERIC SWITCH + }, + { + endpoint_id = 8, + clusters = {{ + cluster_id = clusters.Switch.ID, + feature_map = + clusters.Switch.types.Feature.MOMENTARY_SWITCH | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS, + cluster_type = "SERVER" + },}, + device_types = {{device_type_id = 0x000F, device_type_revision = 1}} -- GENERIC SWITCH + }, + { + endpoint_id = 9, + clusters = {{ + cluster_id = clusters.Switch.ID, + feature_map = + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_LONG_PRESS, + cluster_type = "SERVER"}, + }, + device_types = {{device_type_id = 0x000F, device_type_revision = 1}} -- GENERIC SWITCH + }, + } +}) + +local ENDPOINTS_PRESS = { 3, 6, 9 } + +-- the ikea scroll subdriver has overriden subscribe behavior +local function ikea_scroll_subscribe() + local CLUSTER_SUBSCRIBE_LIST ={ + clusters.Switch.events.InitialPress, + clusters.Switch.server.events.LongPress, + clusters.Switch.server.events.MultiPressComplete, + } + local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_ikea_scroll, ENDPOINTS_PRESS[1]) + for _, ep_press in ipairs(ENDPOINTS_PRESS) do + for _, event in ipairs(CLUSTER_SUBSCRIBE_LIST) do + subscribe_request:merge(event:subscribe(mock_ikea_scroll, ep_press)) + end + end + subscribe_request:merge(clusters.PowerSource.attributes.BatPercentRemaining:subscribe(mock_ikea_scroll, 0)) + return subscribe_request +end + +local function expect_configure_buttons() + local button_attr = capabilities.button.button + test.socket.matter:__expect_send({mock_ikea_scroll.id, clusters.Switch.attributes.MultiPressMax:read(mock_ikea_scroll, 3)}) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("main", button_attr.pushed({state_change = false}))) + test.socket.matter:__expect_send({mock_ikea_scroll.id, clusters.Switch.attributes.MultiPressMax:read(mock_ikea_scroll, 6)}) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("group2", button_attr.pushed({state_change = false}))) + test.socket.matter:__expect_send({mock_ikea_scroll.id, clusters.Switch.attributes.MultiPressMax:read(mock_ikea_scroll, 9)}) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("group3", button_attr.pushed({state_change = false}))) +end + +local function test_init() + test.disable_startup_messages() + test.mock_device.add_test_device(mock_ikea_scroll) + local subscribe_request = ikea_scroll_subscribe() + + test.socket.device_lifecycle:__queue_receive({ mock_ikea_scroll.id, "added" }) + + test.socket.device_lifecycle:__queue_receive({ mock_ikea_scroll.id, "init" }) + test.socket.matter:__expect_send({mock_ikea_scroll.id, subscribe_request}) + + mock_ikea_scroll:expect_metadata_update({ profile = "ikea-scroll" }) + mock_ikea_scroll:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + expect_configure_buttons() + test.socket.device_lifecycle:__queue_receive({ mock_ikea_scroll.id, "doConfigure" }) +end +test.set_test_init_function(test_init) + +test.register_message_test( + "Ensure Ikea Scroll Button initialization works as expected", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.attributes.MultiPressMax:build_test_report_data( + mock_ikea_scroll, 3, 3 + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.button.supportedButtonValues({"pushed", "double", "held", "pushed_3x"}, {visibility = {displayed = false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.attributes.MultiPressMax:build_test_report_data( + mock_ikea_scroll, 6, 3 + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.button.supportedButtonValues({"pushed", "double", "held", "pushed_3x"}, {visibility = {displayed = false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.attributes.MultiPressMax:build_test_report_data( + mock_ikea_scroll, 9, 3 + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group3", + capabilities.button.supportedButtonValues({"pushed", "double", "held", "pushed_3x"}, {visibility = {displayed = false}})) + }, + } +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua b/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua index 22fdbb316a..24987079fc 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua @@ -1,20 +1,10 @@ --- Copyright 2022 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. +-- Copyright © 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" +local st_utils = require "st.utils" local clusters = require "st.matter.clusters" local TRANSITION_TIME = 0 @@ -232,7 +222,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.LevelControl.server.commands.MoveToLevelWithOnOff(mock_device, 1, math.floor(20/100.0 * 254), 20, 0 ,0) + clusters.LevelControl.server.commands.MoveToLevelWithOnOff(mock_device, 1, st_utils.round(20/100.0 * 254), 20, 0 ,0) } }, { @@ -366,6 +356,14 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.colorControl.hue(50)) }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "hue" } + } + }, { channel = "matter", direction = "receive", @@ -378,7 +376,15 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_device:generate_test_message("main", capabilities.colorControl.saturation(50)) - } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "saturation" } + } + }, } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua index 54eb21dc88..c696815a3e 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright © 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" @@ -70,24 +59,17 @@ local mock_bridge = test.mock_device.build_test_matter_device({ } }) -local cluster_subscribe_list = { - clusters.OnOff.attributes.OnOff -} - local function test_init_mock_bridge() - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_bridge) - for i, cluster in ipairs(cluster_subscribe_list) do - if i > 1 then - subscribe_request:merge(cluster:subscribe(mock_bridge)) - end - end - test.socket.matter:__expect_send({mock_bridge.id, subscribe_request}) test.mock_device.add_test_device(mock_bridge) end test.register_coroutine_test( "Profile should not change for devices with aggregator device type (bridges)", function() + test.socket.device_lifecycle:__queue_receive({ mock_bridge.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_bridge.id, "init" }) + test.socket.device_lifecycle:__queue_receive({ mock_bridge.id, "doConfigure" }) + mock_bridge:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, { test_init = test_init_mock_bridge } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua index 23023b3d02..58ba831074 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" @@ -12,6 +15,7 @@ local uint32 = require "st.matter.data_types.Uint32" local mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("button-battery.yml"), manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000}, + matter_version = {hardware = 1, software = 1}, endpoints = { { endpoint_id = 0, diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua new file mode 100644 index 0000000000..1028197590 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -0,0 +1,1889 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local t_utils = require "integration_test.utils" +local test = require "integration_test" +local uint32 = require "st.matter.data_types.Uint32" + +test.disable_startup_messages() + +local CAMERA_EP, FLOODLIGHT_EP, CHIME_EP, DOORBELL_EP = 1, 2, 3, 4 + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("camera.yml"), + manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000}, + matter_version = {hardware = 1, software = 1}, + 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 = CAMERA_EP, + clusters = { + { + cluster_id = clusters.CameraAvStreamManagement.ID, + feature_map = clusters.CameraAvStreamManagement.types.Feature.VIDEO | + clusters.CameraAvStreamManagement.types.Feature.PRIVACY | + clusters.CameraAvStreamManagement.types.Feature.AUDIO | + clusters.CameraAvStreamManagement.types.Feature.LOCAL_STORAGE | + clusters.CameraAvStreamManagement.types.Feature.PRIVACY | + clusters.CameraAvStreamManagement.types.Feature.SPEAKER | + clusters.CameraAvStreamManagement.types.Feature.IMAGE_CONTROL | + clusters.CameraAvStreamManagement.types.Feature.SPEAKER | + clusters.CameraAvStreamManagement.types.Feature.HIGH_DYNAMIC_RANGE | + clusters.CameraAvStreamManagement.types.Feature.NIGHT_VISION | + clusters.CameraAvStreamManagement.types.Feature.WATERMARK | + clusters.CameraAvStreamManagement.types.Feature.ON_SCREEN_DISPLAY, + cluster_type = "SERVER" + }, + { + cluster_id = clusters.CameraAvSettingsUserLevelManagement.ID, + feature_map = clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PAN | + clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_TILT | + clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_ZOOM | + clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PRESETS, + cluster_type = "SERVER" + }, + { + cluster_id = clusters.PushAvStreamTransport.ID, + cluster_type = "SERVER" + }, + { + cluster_id = clusters.ZoneManagement.ID, + feature_map = clusters.ZoneManagement.types.Feature.TWO_DIMENSIONAL_CARTESIAN_ZONE | + clusters.ZoneManagement.types.Feature.PER_ZONE_SENSITIVITY, + cluster_type = "SERVER" + }, + { + cluster_id = clusters.WebRTCTransportProvider.ID, + cluster_type = "SERVER" + }, + { + cluster_id = clusters.WebRTCTransportRequestor.ID, + cluster_type = "CLIENT" + }, + { + cluster_id = clusters.OccupancySensing.ID, + cluster_type = "SERVER" + } + }, + device_types = { + {device_type_id = 0x0142, device_type_revision = 1} -- Camera + } + }, + { + endpoint_id = FLOODLIGHT_EP, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2}, + {cluster_id = clusters.ColorControl.ID, cluster_type = "BOTH", feature_map = 30} + }, + device_types = { + {device_type_id = 0x010D, device_type_revision = 2} -- Extended Color Light + } + }, + { + endpoint_id = CHIME_EP, + clusters = { + { + cluster_id = clusters.Chime.ID, + cluster_type = "SERVER" + }, + }, + device_types = { + {device_type_id = 0x0146, device_type_revision = 1} -- Chime + } + }, + { + endpoint_id = DOORBELL_EP, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_LONG_PRESS, + cluster_type = "SERVER", + } + }, + device_types = { + {device_type_id = 0x0143, device_type_revision = 1} -- Doorbell + } + } + } +}) + +local subscribe_request +local subscribed_attributes = { + clusters.CameraAvStreamManagement.attributes.AttributeList, + clusters.CameraAvStreamManagement.attributes.StatusLightEnabled, + clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel, + clusters.ColorControl.attributes.ColorTemperatureMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, + clusters.ColorControl.attributes.CurrentHue, + clusters.ColorControl.attributes.CurrentSaturation, + clusters.ColorControl.attributes.CurrentX, + clusters.ColorControl.attributes.CurrentY, + clusters.ColorControl.attributes.ColorMode, +} + +local function test_init() + test.mock_device.add_test_device(mock_device) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + local floodlight_child_device_data = { + profile = t_utils.get_profile_definition("light-color-level.yml"), + device_network_id = string.format("%s:%d", mock_device.id, FLOODLIGHT_EP), + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%d", FLOODLIGHT_EP) + } + test.mock_device.add_test_device(test.mock_device.build_test_child_device(floodlight_child_device_data)) + mock_device:expect_device_create({ + type = "EDGE_CHILD", + label = "Floodlight 1", + profile = "light-color-level", + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%d", FLOODLIGHT_EP) + }) + subscribe_request = subscribed_attributes[1]:subscribe(mock_device) + for i, attr in ipairs(subscribed_attributes) do + if i > 1 then subscribe_request:merge(attr:subscribe(mock_device)) end + end + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + +test.set_test_init_function(test_init) + +local additional_subscribed_attributes = { + clusters.CameraAvStreamManagement.attributes.HDRModeEnabled, + clusters.CameraAvStreamManagement.attributes.ImageRotation, + clusters.CameraAvStreamManagement.attributes.NightVision, + clusters.CameraAvStreamManagement.attributes.NightVisionIllum, + clusters.CameraAvStreamManagement.attributes.ImageFlipHorizontal, + clusters.CameraAvStreamManagement.attributes.ImageFlipVertical, + clusters.CameraAvStreamManagement.attributes.SoftRecordingPrivacyModeEnabled, + clusters.CameraAvStreamManagement.attributes.SoftLivestreamPrivacyModeEnabled, + clusters.CameraAvStreamManagement.attributes.HardPrivacyModeOn, + clusters.CameraAvStreamManagement.attributes.TwoWayTalkSupport, + clusters.CameraAvStreamManagement.attributes.SpeakerMuted, + clusters.CameraAvStreamManagement.attributes.MicrophoneMuted, + clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel, + clusters.CameraAvStreamManagement.attributes.SpeakerMaxLevel, + clusters.CameraAvStreamManagement.attributes.SpeakerMinLevel, + clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel, + clusters.CameraAvStreamManagement.attributes.MicrophoneMaxLevel, + clusters.CameraAvStreamManagement.attributes.MicrophoneMinLevel, + clusters.CameraAvStreamManagement.attributes.StatusLightBrightness, + clusters.CameraAvStreamManagement.attributes.StatusLightEnabled, + clusters.CameraAvStreamManagement.attributes.RateDistortionTradeOffPoints, + clusters.CameraAvStreamManagement.attributes.LocalSnapshotRecordingEnabled, + clusters.CameraAvStreamManagement.attributes.LocalVideoRecordingEnabled, + clusters.CameraAvStreamManagement.attributes.MaxEncodedPixelRate, + clusters.CameraAvStreamManagement.attributes.VideoSensorParams, + clusters.CameraAvStreamManagement.attributes.AllocatedVideoStreams, + clusters.CameraAvStreamManagement.attributes.Viewport, + clusters.CameraAvStreamManagement.attributes.MinViewportResolution, + clusters.CameraAvStreamManagement.attributes.AttributeList, + clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPosition, + clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPresets, + clusters.CameraAvSettingsUserLevelManagement.attributes.MaxPresets, + clusters.CameraAvSettingsUserLevelManagement.attributes.ZoomMax, + clusters.CameraAvSettingsUserLevelManagement.attributes.PanMax, + clusters.CameraAvSettingsUserLevelManagement.attributes.PanMin, + clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMax, + clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMin, + clusters.Chime.attributes.InstalledChimeSounds, + clusters.Chime.attributes.SelectedChime, + clusters.ZoneManagement.attributes.MaxZones, + clusters.ZoneManagement.attributes.Zones, + clusters.ZoneManagement.attributes.Triggers, + clusters.ZoneManagement.attributes.SensitivityMax, + clusters.ZoneManagement.attributes.Sensitivity, + clusters.ZoneManagement.events.ZoneTriggered, + clusters.ZoneManagement.events.ZoneStopped, + clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel, + clusters.ColorControl.attributes.ColorTemperatureMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, + clusters.ColorControl.attributes.CurrentHue, + clusters.ColorControl.attributes.CurrentSaturation, + clusters.ColorControl.attributes.CurrentX, + clusters.ColorControl.attributes.CurrentY, + clusters.OccupancySensing.attributes.Occupancy, + clusters.Switch.server.events.InitialPress, + clusters.Switch.server.events.LongPress, + clusters.Switch.server.events.ShortRelease, + clusters.Switch.server.events.MultiPressComplete +} + +local expected_metadata = { + optional_component_capabilities = { + { + "main", + { + "videoCapture2", + "cameraViewportSettings", + "localMediaStorage", + "audioRecording", + "cameraPrivacyMode", + "imageControl", + "hdr", + "nightVision", + "mechanicalPanTiltZoom", + "videoStreamSettings", + "zoneManagement", + "webrtc", + "motionSensor", + "sounds", + } + }, + { + "statusLed", + { + "switch", + "mode" + } + }, + { + "speaker", + { + "audioMute", + "audioVolume" + } + }, + { + "microphone", + { + "audioMute", + "audioVolume" + } + }, + { + "doorbell", + { + "button" + } + } + }, + profile = "camera" +} + +local function update_device_profile() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.AttributeList:build_test_report_data(mock_device, CAMERA_EP, { + uint32(clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID), + uint32(clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID) + }) + }) + mock_device:expect_metadata_update(expected_metadata) + test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, DOORBELL_EP)}) + test.wait_for_events() + local updated_device_profile = t_utils.get_profile_definition( + "camera.yml", {enabled_optional_capabilities = expected_metadata.optional_component_capabilities} + ) + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ profile = updated_device_profile })) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.webrtc.supportedFeatures( + {audio="sendrecv", bundle=true, order="audio/video", supportTrickleICE=true, turnSource="player", video="recvonly"} + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.supportedAttributes( + {"pan", "panRange", "tilt", "tiltRange", "zoom", "zoomRange", "presets", "maxPresets"} + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.zoneManagement.supportedFeatures( + {"triggerAugmentation", "perZoneSensitivity"} + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.localMediaStorage.supportedAttributes( + {"localVideoRecording"} + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.audioRecording.audioRecording("enabled")) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.supportedFeatures( + {"liveStreaming", "clipRecording", "perStreamViewports", "watermark", "onScreenDisplay"} + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.cameraPrivacyMode.supportedAttributes( + {"softRecordingPrivacyMode", "softLivestreamPrivacyMode"} + )) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.cameraPrivacyMode.supportedCommands( + {"setSoftRecordingPrivacyMode", "setSoftLivestreamPrivacyMode"} + )) + ) + for _, attr in ipairs(additional_subscribed_attributes) do + subscribe_request:merge(attr:subscribe(mock_device)) + end + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, DOORBELL_EP)}) + test.socket.capability:__expect_send(mock_device:generate_test_message("doorbell", capabilities.button.button.pushed({state_change = false}))) +end + +-- Matter Handler UTs + +test.register_coroutine_test( + "Reports mapping to EnabledState capability data type should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + local cluster_to_capability_map = { + {cluster = clusters.CameraAvStreamManagement.server.attributes.HDRModeEnabled, capability = capabilities.hdr.hdr}, + {cluster = clusters.CameraAvStreamManagement.server.attributes.ImageFlipHorizontal, capability = capabilities.imageControl.imageFlipHorizontal}, + {cluster = clusters.CameraAvStreamManagement.server.attributes.ImageFlipVertical, capability = capabilities.imageControl.imageFlipVertical}, + {cluster = clusters.CameraAvStreamManagement.server.attributes.SoftRecordingPrivacyModeEnabled, capability = capabilities.cameraPrivacyMode.softRecordingPrivacyMode}, + {cluster = clusters.CameraAvStreamManagement.server.attributes.SoftLivestreamPrivacyModeEnabled, capability = capabilities.cameraPrivacyMode.softLivestreamPrivacyMode}, + {cluster = clusters.CameraAvStreamManagement.server.attributes.HardPrivacyModeOn, capability = capabilities.cameraPrivacyMode.hardPrivacyMode}, + {cluster = clusters.CameraAvStreamManagement.server.attributes.LocalSnapshotRecordingEnabled, capability = capabilities.localMediaStorage.localSnapshotRecording}, + {cluster = clusters.CameraAvStreamManagement.server.attributes.LocalVideoRecordingEnabled, capability = capabilities.localMediaStorage.localVideoRecording} + } + for _, v in ipairs(cluster_to_capability_map) do + test.socket.matter:__queue_receive({ + mock_device.id, + v.cluster:build_test_report_data(mock_device, CAMERA_EP, true) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", v.capability("enabled")) + ) + if v.capability == capabilities.imageControl.imageFlipHorizontal then + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.imageControl.supportedAttributes({"imageFlipHorizontal"})) + ) + elseif v.capability == capabilities.imageControl.imageFlipVertical then + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.imageControl.supportedAttributes({"imageFlipHorizontal", "imageFlipVertical"})) + ) + elseif v.capability == capabilities.cameraPrivacyMode.hardPrivacyMode then + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.cameraPrivacyMode.supportedAttributes({"softRecordingPrivacyMode", "softLivestreamPrivacyMode", "hardPrivacyMode"})) + ) + end + test.socket.matter:__queue_receive({ + mock_device.id, + v.cluster:build_test_report_data(mock_device, CAMERA_EP, false) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", v.capability("disabled")) + ) + end + end +) + +test.register_coroutine_test( + "Night Vision reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + local cluster_to_capability_map = { + {cluster = clusters.CameraAvStreamManagement.server.attributes.NightVision, capability = capabilities.nightVision.nightVision}, + {cluster = clusters.CameraAvStreamManagement.server.attributes.NightVisionIllum, capability = capabilities.nightVision.illumination} + } + for _, v in ipairs(cluster_to_capability_map) do + test.socket.matter:__queue_receive({ + mock_device.id, + v.cluster:build_test_report_data(mock_device, CAMERA_EP, clusters.CameraAvStreamManagement.types.TriStateAutoEnum.OFF) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", v.capability("off")) + ) + if v.capability == capabilities.nightVision.illumination then + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.nightVision.supportedAttributes({"illumination"})) + ) + end + test.socket.matter:__queue_receive({ + mock_device.id, + v.cluster:build_test_report_data(mock_device, CAMERA_EP, clusters.CameraAvStreamManagement.types.TriStateAutoEnum.ON) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", v.capability("on")) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + v.cluster:build_test_report_data(mock_device, CAMERA_EP, clusters.CameraAvStreamManagement.types.TriStateAutoEnum.AUTO) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", v.capability("auto")) + ) + end + end +) + +test.register_coroutine_test( + "Image Rotation reports should generate appropriate events", + function() + local utils = require "st.utils" + update_device_profile() + test.wait_for_events() + local first_value = true + for angle = 0, 400, 50 do + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.ImageRotation:build_test_report_data(mock_device, CAMERA_EP, angle) + }) + local clamped_angle = utils.clamp_value(angle, 0, 359) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.imageControl.imageRotation(clamped_angle)) + ) + if first_value then + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.imageControl.supportedAttributes({"imageRotation"})) + ) + first_value = false + end + end + end +) + +test.register_coroutine_test( + "Two Way Talk Support reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.TwoWayTalkSupport:build_test_report_data( + mock_device, CAMERA_EP, clusters.CameraAvStreamManagement.types.TwoWayTalkSupportTypeEnum.HALF_DUPLEX + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.webrtc.talkback(true)) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.webrtc.talkbackDuplex("halfDuplex")) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.TwoWayTalkSupport:build_test_report_data( + mock_device, CAMERA_EP, clusters.CameraAvStreamManagement.types.TwoWayTalkSupportTypeEnum.FULL_DUPLEX + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.webrtc.talkback(true)) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.webrtc.talkbackDuplex("fullDuplex")) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.TwoWayTalkSupport:build_test_report_data( + mock_device, CAMERA_EP, clusters.CameraAvStreamManagement.types.TwoWayTalkSupportTypeEnum.NOT_SUPPORTED + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.webrtc.talkback(false)) + ) + end +) + +test.register_coroutine_test( + "Muted reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + local cluster_to_component_map = { + {cluster = clusters.CameraAvStreamManagement.server.attributes.SpeakerMuted, component = "speaker"}, + {cluster = clusters.CameraAvStreamManagement.server.attributes.MicrophoneMuted, component = "microphone"} + } + for _, v in ipairs(cluster_to_component_map) do + test.socket.matter:__queue_receive({ + mock_device.id, + v.cluster:build_test_report_data(mock_device, CAMERA_EP, true) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message(v.component, capabilities.audioMute.mute("muted")) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + v.cluster:build_test_report_data(mock_device, CAMERA_EP, false) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message(v.component, capabilities.audioMute.mute("unmuted")) + ) + end + end +) + +test.register_coroutine_test( + "Volume Level reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + local max_vol = 200 + local min_vol = 0 + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.SpeakerMaxLevel:build_test_report_data(mock_device, CAMERA_EP, max_vol) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.SpeakerMinLevel:build_test_report_data(mock_device, CAMERA_EP, min_vol) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.MicrophoneMaxLevel:build_test_report_data(mock_device, CAMERA_EP, max_vol) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.MicrophoneMinLevel:build_test_report_data(mock_device, CAMERA_EP, min_vol) + }) + test.wait_for_events() + local cluster_to_component_map = { + { cluster = clusters.CameraAvStreamManagement.server.attributes.SpeakerVolumeLevel, component = "speaker"}, + { cluster = clusters.CameraAvStreamManagement.server.attributes.MicrophoneVolumeLevel, component = "microphone"} + } + for _, v in ipairs(cluster_to_component_map) do + test.socket.matter:__queue_receive({ + mock_device.id, + v.cluster:build_test_report_data(mock_device, CAMERA_EP, 130) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message(v.component, capabilities.audioVolume.volume(65)) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + v.cluster:build_test_report_data(mock_device, CAMERA_EP, 64) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message(v.component, capabilities.audioVolume.volume(32)) + ) + end + end +) + +test.register_coroutine_test( + "Status Light Enabled reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.StatusLightEnabled:build_test_report_data(mock_device, CAMERA_EP, true) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("statusLed", capabilities.switch.switch.on()) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.StatusLightEnabled:build_test_report_data(mock_device, CAMERA_EP, false) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("statusLed", capabilities.switch.switch.off()) + ) + end +) + +test.register_coroutine_test( + "Status Light Brightness reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.StatusLightBrightness:build_test_report_data( + mock_device, CAMERA_EP, clusters.Global.types.ThreeLevelAutoEnum.LOW) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("statusLed", capabilities.mode.supportedModes( + {"low", "medium", "high", "auto"}, {visibility = {displayed = false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("statusLed", capabilities.mode.supportedArguments( + {"low", "medium", "high", "auto"}, {visibility = {displayed = false}}) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("statusLed", capabilities.mode.mode("low")) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.StatusLightBrightness:build_test_report_data( + mock_device, CAMERA_EP, clusters.Global.types.ThreeLevelAutoEnum.MEDIUM) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("statusLed", capabilities.mode.mode("medium")) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.StatusLightBrightness:build_test_report_data( + mock_device, CAMERA_EP, clusters.Global.types.ThreeLevelAutoEnum.HIGH) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("statusLed", capabilities.mode.mode("high")) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.StatusLightBrightness:build_test_report_data( + mock_device, CAMERA_EP, clusters.Global.types.ThreeLevelAutoEnum.AUTO) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("statusLed", capabilities.mode.mode("auto")) + ) + end +) + +local function receive_rate_distortion_trade_off_points() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.RateDistortionTradeOffPoints:build_test_report_data( + mock_device, CAMERA_EP, { + clusters.CameraAvStreamManagement.types.RateDistortionTradeOffPointsStruct({ + codec = clusters.CameraAvStreamManagement.types.VideoCodecEnum.H264, + resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({ + width = 1920, + height = 1080 + }), + min_bit_rate = 5000000 + }), + clusters.CameraAvStreamManagement.types.RateDistortionTradeOffPointsStruct({ + codec = clusters.CameraAvStreamManagement.types.VideoCodecEnum.HEVC, + resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({ + width = 3840, + height = 2160 + }), + min_bit_rate = 20000000 + }) + } + ) + }) +end + +local function receive_max_encoded_pixel_rate() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.MaxEncodedPixelRate:build_test_report_data( + mock_device, CAMERA_EP, 124416000) -- 1080p @ 60 fps or 4K @ 15 fps + }) +end + +local function receive_video_sensor_params() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.VideoSensorParams:build_test_report_data( + mock_device, CAMERA_EP, clusters.CameraAvStreamManagement.types.VideoSensorParamsStruct({ + sensor_width = 7360, + sensor_height = 4912, + max_fps = 60, + max_hdrfps = 30 + }) + ) + }) +end + +local function emit_video_sensor_parameters() + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.cameraViewportSettings.videoSensorParameters({ + width = 7360, + height = 4912, + maxFPS = 60 + })) + ) +end + +local function emit_supported_resolutions() + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.supportedResolutions({ + { + width = 1920, + height = 1080, + fps = 60 + }, + { + width = 3840, + height = 2160, + fps = 15 + } + })) + ) +end + +-- Test receiving RateDistortionTradeOffPoints, MaxEncodedPixelRate, and VideoSensorParams in various orders +-- to ensure that cameraViewportSettings and videoStreamSettings capabilities are updated as expected. Note that +-- cameraViewportSettings.videoSensorParameters is set in the VideoSensorParams handler and +-- videoStreamSettings.supportedResolutions is emitted after all three attributes are received. + +test.register_coroutine_test( + "Rate Distortion Trade Off Points, MaxEncodedPixelRate, VideoSensorParams reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + receive_rate_distortion_trade_off_points() + receive_max_encoded_pixel_rate() + receive_video_sensor_params() + emit_video_sensor_parameters() + emit_supported_resolutions() + end +) + +test.register_coroutine_test( + "Rate Distortion Trade Off Points, VideoSensorParams, MaxEncodedPixelRate reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + receive_rate_distortion_trade_off_points() + receive_video_sensor_params() + emit_video_sensor_parameters() + receive_max_encoded_pixel_rate() + emit_supported_resolutions() + end +) + +test.register_coroutine_test( + "MaxEncodedPixelRate, VideoSensorParams, Rate Distortion Trade Off Points reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + receive_max_encoded_pixel_rate() + receive_video_sensor_params() + emit_video_sensor_parameters() + receive_rate_distortion_trade_off_points() + emit_supported_resolutions() + end +) + +test.register_coroutine_test( + "PTZ Position reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.PanMax:build_test_report_data(mock_device, CAMERA_EP, 150) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.PanMin:build_test_report_data(mock_device, CAMERA_EP, -150) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.panRange({value = {minimum = -150, maximum = 150}})) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMax:build_test_report_data(mock_device, CAMERA_EP, 80) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMin:build_test_report_data(mock_device, CAMERA_EP, -80) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.tiltRange({value = {minimum = -80, maximum = 80}})) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.ZoomMax:build_test_report_data(mock_device, CAMERA_EP, 70) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.zoomRange({value = {minimum = 1, maximum = 70}})) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPosition:build_test_report_data( + mock_device, CAMERA_EP, {pan = 10, tilt = 20, zoom = 30}) + }) + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.pan(10)) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.tilt(20)) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.zoom(30)) + ) + end +) + +test.register_coroutine_test( + "PTZ Presets reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPresets:build_test_report_data( + mock_device, CAMERA_EP, {{preset_id = 1, name = "Preset 1", settings = {pan = 10, tilt = 20, zoom = 30}}, + {preset_id = 2, name = "Preset 2", settings = {pan = -55, tilt = 80, zoom = 60}}} + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.presets({ + { id = 1, label = "Preset 1", pan = 10, tilt = 20, zoom = 30}, + { id = 2, label = "Preset 2", pan = -55, tilt = 80, zoom = 60} + })) + ) + end +) + +test.register_coroutine_test( + "Max Presets reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.MaxPresets:build_test_report_data(mock_device, CAMERA_EP, 10) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.maxPresets(10)) + ) + end +) + +test.register_coroutine_test( + "Max Zones reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ZoneManagement.attributes.MaxZones:build_test_report_data(mock_device, CAMERA_EP, 10) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.zoneManagement.maxZones(10)) + ) + end +) + +test.register_coroutine_test( + "Zones reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ZoneManagement.attributes.Zones:build_test_report_data( + mock_device, CAMERA_EP, { + clusters.ZoneManagement.types.ZoneInformationStruct({ + zone_id = 1, + zone_type = clusters.ZoneManagement.types.ZoneTypeEnum.TWODCART_ZONE, + zone_source = clusters.ZoneManagement.types.ZoneSourceEnum.MFG, + two_d_cartesian_zone = clusters.ZoneManagement.types.TwoDCartesianZoneStruct({ + name = "Zone 1", + use = clusters.ZoneManagement.types.ZoneUseEnum.MOTION, + vertices = { + clusters.ZoneManagement.types.TwoDCartesianVertexStruct({ x = 0, y = 0 }), + clusters.ZoneManagement.types.TwoDCartesianVertexStruct({ x = 1920, y = 1080 }) + }, + color = "#FFFFFF" + }) + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.zoneManagement.zones({ + { + id = 1, + name = "Zone 1", + type = "2DCartesian", + polygonVertices = { + {vertex = {x = 0, y = 0}}, + {vertex = {x = 1920, y = 1080}} + }, + source = "manufacturer", + use = "motion", + color = "#FFFFFF" + } + })) + ) + end +) + +test.register_coroutine_test( + "Triggers reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ZoneManagement.attributes.Triggers:build_test_report_data( + mock_device, CAMERA_EP, { + clusters.ZoneManagement.types.ZoneTriggerControlStruct({ + zone_id = 1, + initial_duration = 8, + augmentation_duration = 4, + max_duration = 20, + blind_duration = 3, + sensitivity = 4 + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.zoneManagement.triggers({ + { + zoneId = 1, + initialDuration = 8, + augmentationDuration = 4, + maxDuration = 20, + blindDuration = 3, + sensitivity = 4 + } + })) + ) + end +) + +test.register_coroutine_test( + "Sensitivity reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ZoneManagement.attributes.SensitivityMax:build_test_report_data(mock_device, CAMERA_EP, 7) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.zoneManagement.sensitivityRange({ minimum = 1, maximum = 7}, + {visibility = {displayed = false}})) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ZoneManagement.attributes.Sensitivity:build_test_report_data(mock_device, CAMERA_EP, 5) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.zoneManagement.sensitivity(5, {visibility = {displayed = false}})) + ) + end +) + +test.register_coroutine_test( + "Chime reports should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Chime.attributes.InstalledChimeSounds:build_test_report_data(mock_device, CAMERA_EP, { + clusters.Chime.types.ChimeSoundStruct({chime_id = 1, name = "Sound 1"}), + clusters.Chime.types.ChimeSoundStruct({chime_id = 2, name = "Sound 2"}) + }) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.sounds.supportedSounds({ + {id = 1, label = "Sound 1"}, + {id = 2, label = "Sound 2"}, + }, {visibility = {displayed = false}})) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Chime.attributes.SelectedChime:build_test_report_data(mock_device, CAMERA_EP, 2) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.sounds.selectedSound(2))) + end +) + +-- Event Handler UTs + +test.register_coroutine_test( + "Zone events should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ZoneManagement.events.ZoneTriggered:build_test_event_report(mock_device, CAMERA_EP, { + zone = 2, + reason = clusters.ZoneManagement.types.ZoneEventTriggeredReasonEnum.MOTION + }) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.zoneManagement.triggeredZones({{zoneId = 2}})) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ZoneManagement.events.ZoneTriggered:build_test_event_report(mock_device, CAMERA_EP, { + zone = 3, + reason = clusters.ZoneManagement.types.ZoneEventTriggeredReasonEnum.MOTION + }) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.zoneManagement.triggeredZones({{zoneId = 2}, {zoneId = 3}})) + ) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ZoneManagement.events.ZoneStopped:build_test_event_report(mock_device, CAMERA_EP, { + zone = 2, + reason = clusters.ZoneManagement.types.ZoneEventStoppedReasonEnum.ACTION_STOPPED + }) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.zoneManagement.triggeredZones({{zoneId = 3}})) + ) + end +) + +test.register_coroutine_test( + "Button events should generate appropriate events", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.server.events.InitialPress:build_test_event_report(mock_device, DOORBELL_EP, {new_position = 1}) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.server.events.MultiPressComplete:build_test_event_report(mock_device, DOORBELL_EP, { + new_position = 1, + total_number_of_presses_counted = 2, + previous_position = 0 + }) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("doorbell", capabilities.button.button.double({state_change = true})) + ) + end +) + +-- Capability Handler UTs + +test.register_coroutine_test( + "Set night vision commands should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + local command_to_attribute_map = { + ["setNightVision"] = clusters.CameraAvStreamManagement.attributes.NightVision, + ["setIllumination"] = clusters.CameraAvStreamManagement.attributes.NightVisionIllum + } + for cmd, attr in pairs(command_to_attribute_map) do + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "nightVision", component = "main", command = cmd, args = { "off" } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, attr:write(mock_device, CAMERA_EP, clusters.CameraAvStreamManagement.types.TriStateAutoEnum.OFF) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "nightVision", component = "main", command = cmd, args = { "on" } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, attr:write(mock_device, CAMERA_EP, clusters.CameraAvStreamManagement.types.TriStateAutoEnum.ON) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "nightVision", component = "main", command = cmd, args = { "auto" } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, attr:write(mock_device, CAMERA_EP, clusters.CameraAvStreamManagement.types.TriStateAutoEnum.AUTO) + }) + end + end +) + +test.register_coroutine_test( + "Set enabled commands should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + local command_to_attribute_map = { + ["setHdr"] = { capability = "hdr", attr = clusters.CameraAvStreamManagement.attributes.HDRModeEnabled}, + ["setImageFlipHorizontal"] = { capability = "imageControl", attr = clusters.CameraAvStreamManagement.attributes.ImageFlipHorizontal}, + ["setImageFlipVertical"] = { capability = "imageControl", attr = clusters.CameraAvStreamManagement.attributes.ImageFlipVertical}, + ["setSoftLivestreamPrivacyMode"] = { capability = "cameraPrivacyMode", attr = clusters.CameraAvStreamManagement.attributes.SoftLivestreamPrivacyModeEnabled}, + ["setSoftRecordingPrivacyMode"] = { capability = "cameraPrivacyMode", attr = clusters.CameraAvStreamManagement.attributes.SoftRecordingPrivacyModeEnabled}, + ["setLocalSnapshotRecording"] = { capability = "localMediaStorage", attr = clusters.CameraAvStreamManagement.attributes.LocalSnapshotRecordingEnabled}, + ["setLocalVideoRecording"] = { capability = "localMediaStorage", attr = clusters.CameraAvStreamManagement.attributes.LocalVideoRecordingEnabled} + } + for i, v in pairs(command_to_attribute_map) do + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = v.capability, component = "main", command = i, args = { "enabled" } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, v.attr:write(mock_device, CAMERA_EP, true) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = v.capability, component = "main", command = i, args = { "disabled" } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, v.attr:write(mock_device, CAMERA_EP, false) + }) + end + end +) + +test.register_coroutine_test( + "Set image rotation command should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "imageControl", component = "main", command = "setImageRotation", args = { 10 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.ImageRotation:write(mock_device, CAMERA_EP, 10) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "imageControl", component = "main", command = "setImageRotation", args = { 257 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.ImageRotation:write(mock_device, CAMERA_EP, 257) + }) + end +) + +test.register_coroutine_test( + "Set mute commands should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "audioMute", component = "speaker", command = "setMute", args = { "muted" } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.SpeakerMuted:write(mock_device, CAMERA_EP, true) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "audioMute", component = "speaker", command = "setMute", args = { "unmuted" } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.SpeakerMuted:write(mock_device, CAMERA_EP, false) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "audioMute", component = "speaker", command = "mute", args = { } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.SpeakerMuted:write(mock_device, CAMERA_EP, true) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "audioMute", component = "speaker", command = "unmute", args = { } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.SpeakerMuted:write(mock_device, CAMERA_EP, false) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "audioMute", component = "microphone", command = "setMute", args = { "muted" } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.MicrophoneMuted:write(mock_device, CAMERA_EP, true) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "audioMute", component = "microphone", command = "setMute", args = { "unmuted" } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.MicrophoneMuted:write(mock_device, CAMERA_EP, false) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "audioMute", component = "microphone", command = "mute", args = { } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.MicrophoneMuted:write(mock_device, CAMERA_EP, true) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "audioMute", component = "microphone", command = "unmute", args = { } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.MicrophoneMuted:write(mock_device, CAMERA_EP, false) + }) + end +) + +test.register_coroutine_test( + "Set Volume command should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + local max_vol = 200 + local min_vol = 5 + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.SpeakerMaxLevel:build_test_report_data(mock_device, CAMERA_EP, max_vol) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.SpeakerMinLevel:build_test_report_data(mock_device, CAMERA_EP, min_vol) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.MicrophoneMaxLevel:build_test_report_data(mock_device, CAMERA_EP, max_vol) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.server.attributes.MicrophoneMinLevel:build_test_report_data(mock_device, CAMERA_EP, min_vol) + }) + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "audioVolume", component = "speaker", command = "setVolume", args = { 0 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel:write(mock_device, CAMERA_EP, 5) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "audioVolume", component = "speaker", command = "setVolume", args = { 35 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel:write(mock_device, CAMERA_EP, 73) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "audioVolume", component = "microphone", command = "setVolume", args = { 77 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel:write(mock_device, CAMERA_EP, 155) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "audioVolume", component = "microphone", command = "setVolume", args = { 100 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel:write(mock_device, CAMERA_EP, 200) + }) + + ---- test volumeUp command + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel:build_test_report_data(mock_device, CAMERA_EP, 103) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("speaker", capabilities.audioVolume.volume(50)) + ) + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "audioVolume", component = "speaker", command = "volumeUp", args = { } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel:write(mock_device, CAMERA_EP, 104) + }) + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel:build_test_report_data(mock_device, CAMERA_EP, 104) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("speaker", capabilities.audioVolume.volume(51)) + ) + + -- test volumeDown command + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel:build_test_report_data(mock_device, CAMERA_EP, 200) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("microphone", capabilities.audioVolume.volume(100)) + ) + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "audioVolume", component = "microphone", command = "volumeDown", args = { } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel:write(mock_device, CAMERA_EP, 198) + }) + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel:build_test_report_data(mock_device, CAMERA_EP, 198) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("microphone", capabilities.audioVolume.volume(99)) + ) + end +) + +test.register_coroutine_test( + "Set Mode command should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + local mode_to_enum_map = { + ["low"] = clusters.Global.types.ThreeLevelAutoEnum.LOW, + ["medium"] = clusters.Global.types.ThreeLevelAutoEnum.MEDIUM, + ["high"] = clusters.Global.types.ThreeLevelAutoEnum.HIGH, + ["auto"] = clusters.Global.types.ThreeLevelAutoEnum.AUTO + } + for i, v in pairs(mode_to_enum_map) do + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mode", component = "speaker", command = "setMode", args = { i } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.StatusLightBrightness:write(mock_device, CAMERA_EP, v) + }) + end + end +) + +test.register_coroutine_test( + "Set Status LED commands should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "switch", component = "statusLed", command = "on", args = { } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.StatusLightEnabled:write(mock_device, CAMERA_EP, true) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "switch", component = "statusLed", command = "off", args = { } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.attributes.StatusLightEnabled:write(mock_device, CAMERA_EP, false) + }) + end +) + +test.register_coroutine_test( + "Set Relative PTZ commands should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mechanicalPanTiltZoom", component = "main", command = "panRelative", args = { 10 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove(mock_device, CAMERA_EP, 10, 0, 0) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mechanicalPanTiltZoom", component = "main", command = "tiltRelative", args = { -35 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove(mock_device, CAMERA_EP, 0, -35, 0) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mechanicalPanTiltZoom", component = "main", command = "zoomRelative", args = { 80 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove(mock_device, CAMERA_EP, 0, 0, 80) + }) + end +) + +test.register_coroutine_test( + "Set PTZ commands should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mechanicalPanTiltZoom", component = "main", command = "setPanTiltZoom", args = { 10, 20, 30 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZSetPosition(mock_device, CAMERA_EP, 10, 20, 30) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPosition:build_test_report_data( + mock_device, CAMERA_EP, {pan = 10, tilt = 20, zoom = 30}) + }) + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.pan(10)) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.tilt(20)) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.zoom(30)) + ) + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mechanicalPanTiltZoom", component = "main", command = "setPan", args = { 50 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZSetPosition(mock_device, CAMERA_EP, 50, 20, 30) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPosition:build_test_report_data( + mock_device, CAMERA_EP, {pan = 50, tilt = 20, zoom = 30}) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.pan(50)) + ) + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mechanicalPanTiltZoom", component = "main", command = "setTilt", args = { -44 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZSetPosition(mock_device, CAMERA_EP, 50, -44, 30) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPosition:build_test_report_data( + mock_device, CAMERA_EP, {pan = 50, tilt = -44, zoom = 30}) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.tilt(-44)) + ) + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mechanicalPanTiltZoom", component = "main", command = "setZoom", args = { 5 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZSetPosition(mock_device, CAMERA_EP, 50, -44, 5) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPosition:build_test_report_data( + mock_device, CAMERA_EP, {pan = 50, tilt = -44, zoom = 5}) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.mechanicalPanTiltZoom.zoom(5)) + ) + end +) + +test.register_coroutine_test( + "Preset commands should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mechanicalPanTiltZoom", component = "main", command = "savePreset", args = { 1, "Preset 1" } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZSavePreset(mock_device, CAMERA_EP, 1, "Preset 1") + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mechanicalPanTiltZoom", component = "main", command = "removePreset", args = { 1 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRemovePreset(mock_device, CAMERA_EP, 1) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "mechanicalPanTiltZoom", component = "main", command = "moveToPreset", args = { 2 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZMoveToPreset(mock_device, CAMERA_EP, 2) + }) + end +) + +test.register_coroutine_test( + "Sound commands should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "sounds", component = "main", command = "setSelectedSound", args = { 1 } }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.Chime.attributes.SelectedChime:write(mock_device, CAMERA_EP, 1) + }) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "sounds", component = "main", command = "playSound", args = {} }, + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.Chime.server.commands.PlayChimeSound(mock_device, CAMERA_EP) + }) + end +) + +test.register_coroutine_test( + "Zone Management zone commands should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + local use_map = { + ["motion"] = clusters.ZoneManagement.types.ZoneUseEnum.MOTION, + ["focus"] = clusters.ZoneManagement.types.ZoneUseEnum.FOCUS, + ["privacy"] = clusters.ZoneManagement.types.ZoneUseEnum.PRIVACY + } + for i, v in pairs(use_map) do + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "zoneManagement", component = "main", command = "newZone", args = { + i .. " zone", {{value = {x = 0, y = 0}}, {value = {x = 1920, y = 1080}} }, i, "blue" + }} + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.ZoneManagement.server.commands.CreateTwoDCartesianZone(mock_device, CAMERA_EP, + clusters.ZoneManagement.types.TwoDCartesianZoneStruct( + { + name = i .. " zone", + use = v, + vertices = { + clusters.ZoneManagement.types.TwoDCartesianVertexStruct({x = 0, y = 0}), + clusters.ZoneManagement.types.TwoDCartesianVertexStruct({x = 1920, y = 1080}) + }, + color = "blue" + } + ) + ) + }) + end + local zone_id = 1 + for i, v in pairs(use_map) do + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "zoneManagement", component = "main", command = "updateZone", args = { + zone_id, "updated " .. i .. " zone", {{value = {x = 50, y = 50}}, {value = {x = 1000, y = 1000}} }, i, "red" + }} + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.ZoneManagement.server.commands.UpdateTwoDCartesianZone(mock_device, CAMERA_EP, + zone_id, + clusters.ZoneManagement.types.TwoDCartesianZoneStruct( + { + name = "updated " .. i .. " zone", + use = v, + vertices = { + clusters.ZoneManagement.types.TwoDCartesianVertexStruct({ x = 50, y = 50 }), + clusters.ZoneManagement.types.TwoDCartesianVertexStruct({ x = 1000, y = 1000 }) + }, + color = "red" + } + ) + ) + }) + zone_id = zone_id + 1 + end + for i = 1, 3 do + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "zoneManagement", component = "main", command = "removeZone", args = { i } } + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.ZoneManagement.server.commands.RemoveZone(mock_device, CAMERA_EP, i) + }) + end + end +) + +test.register_coroutine_test( + "Zone Management zone commands should send the appropriate commands - missing optional color argument", + function() + update_device_profile() + test.wait_for_events() + local use_map = { + ["motion"] = clusters.ZoneManagement.types.ZoneUseEnum.MOTION, + ["focus"] = clusters.ZoneManagement.types.ZoneUseEnum.FOCUS, + ["privacy"] = clusters.ZoneManagement.types.ZoneUseEnum.PRIVACY + } + for i, v in pairs(use_map) do + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "zoneManagement", component = "main", command = "newZone", args = { + i .. " zone", {{value = {x = 0, y = 0}}, {value = {x = 1920, y = 1080}} }, i + }} + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.ZoneManagement.server.commands.CreateTwoDCartesianZone(mock_device, CAMERA_EP, + clusters.ZoneManagement.types.TwoDCartesianZoneStruct( + { + name = i .. " zone", + use = v, + vertices = { + clusters.ZoneManagement.types.TwoDCartesianVertexStruct({x = 0, y = 0}), + clusters.ZoneManagement.types.TwoDCartesianVertexStruct({x = 1920, y = 1080}) + }, + } + ) + ) + }) + end + local zone_id = 1 + for i, v in pairs(use_map) do + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "zoneManagement", component = "main", command = "updateZone", args = { + zone_id, "updated " .. i .. " zone", {{value = {x = 50, y = 50}}, {value = {x = 1000, y = 1000}} }, i, "red" + }} + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.ZoneManagement.server.commands.UpdateTwoDCartesianZone(mock_device, CAMERA_EP, + zone_id, + clusters.ZoneManagement.types.TwoDCartesianZoneStruct( + { + name = "updated " .. i .. " zone", + use = v, + vertices = { + clusters.ZoneManagement.types.TwoDCartesianVertexStruct({ x = 50, y = 50 }), + clusters.ZoneManagement.types.TwoDCartesianVertexStruct({ x = 1000, y = 1000 }) + }, + color = "red" + } + ) + ) + }) + zone_id = zone_id + 1 + end + for i = 1, 3 do + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "zoneManagement", component = "main", command = "removeZone", args = { i } } + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.ZoneManagement.server.commands.RemoveZone(mock_device, CAMERA_EP, i) + }) + end + end +) + +test.register_coroutine_test( + "Zone Management trigger commands should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + + -- Create the trigger + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "zoneManagement", component = "main", command = "createOrUpdateTrigger", args = { + 1, 10, 3, 15, 3, 5 + }} + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.ZoneManagement.server.commands.CreateOrUpdateTrigger(mock_device, CAMERA_EP, { + zone_id = 1, + initial_duration = 10, + augmentation_duration = 3, + max_duration = 15, + blind_duration = 3, + sensitivity = 5 + }) + }) + + -- The device reports the Triggers attribute with the newly created trigger and the capability is updated + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ZoneManagement.attributes.Triggers:build_test_report_data( + mock_device, CAMERA_EP, { + clusters.ZoneManagement.types.ZoneTriggerControlStruct({ + zone_id = 1, initial_duration = 10, augmentation_duration = 3, max_duration = 15, blind_duration = 3, sensitivity = 5 + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.zoneManagement.triggers({{ + zoneId = 1, initialDuration = 10, augmentationDuration = 3, maxDuration = 15, blindDuration = 3, sensitivity = 5 + }})) + ) + test.wait_for_events() + + -- Update trigger, note that some arguments are optional. In this case, + -- blindDuration is not specified in the capability command. + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "zoneManagement", component = "main", command = "createOrUpdateTrigger", args = { + 1, 8, 7, 25, 3, 1 + }} + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.ZoneManagement.server.commands.CreateOrUpdateTrigger(mock_device, CAMERA_EP, { + zone_id = 1, + initial_duration = 8, + augmentation_duration = 7, + max_duration = 25, + blind_duration = 3, + sensitivity = 1 + }) + }) + + -- Remove the trigger + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "zoneManagement", component = "main", command = "removeTrigger", args = { 1 } } + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.ZoneManagement.server.commands.RemoveTrigger(mock_device, CAMERA_EP, 1) + }) + end +) + +test.register_coroutine_test( + "Stream management commands should send the appropriate commands", + function() + update_device_profile() + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "videoStreamSettings", component = "main", command = "setStream", args = { + 3, + "liveStream", + "Stream 3", + { width = 1920, height = 1080, fps = 30 }, + { upperLeftVertex = {x = 0, y = 0}, lowerRightVertex = {x = 1920, y = 1080} }, + "enabled", + "disabled" + }} + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.server.commands.VideoStreamModify(mock_device, CAMERA_EP, + 3, true, false + ) + }) + end +) + +test.register_coroutine_test( + "Stream management setStream command should modify an existing stream", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.AllocatedVideoStreams:build_test_report_data( + mock_device, CAMERA_EP, { + clusters.CameraAvStreamManagement.types.VideoStreamStruct({ + video_stream_id = 1, + stream_usage = clusters.Global.types.StreamUsageEnum.LIVE_VIEW, + video_codec = clusters.CameraAvStreamManagement.types.VideoCodecEnum.H264, + min_frame_rate = 30, + max_frame_rate = 60, + min_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 640, height = 360}), + max_resolution = clusters.CameraAvStreamManagement.types.VideoResolutionStruct({width = 640, height = 360}), + min_bit_rate = 10000, + max_bit_rate = 10000, + key_frame_interval = 4000, + watermark_enabled = true, + osd_enabled = false, + reference_count = 0 + }) + } + ) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.videoStreamSettings.videoStreams({ + { + streamId = 1, + data = { + label = "Stream 1", + type = "liveStream", + resolution = { + width = 640, + height = 360, + fps = 30 + }, + watermark = "enabled", + onScreenDisplay = "disabled" + } + } + })) + ) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "videoStreamSettings", component = "main", command = "setStream", args = { + 1, + "liveStream", + "Stream 1", + { width = 640, height = 360, fps = 30 }, + { upperLeftVertex = {x = 0, y = 0}, lowerRightVertex = {x = 640, y = 360} }, + "disabled", + "enabled" + }} + }) + test.socket.matter:__expect_send({ + mock_device.id, clusters.CameraAvStreamManagement.server.commands.VideoStreamModify(mock_device, CAMERA_EP, + 1, false, true + ) + }) + end +) + +test.register_coroutine_test( + "Camera profile should not update for an unchanged Status Light AttributeList report", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.AttributeList:build_test_report_data(mock_device, CAMERA_EP, { + uint32(clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID), + uint32(clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID) + }) + }) + end +) + +test.register_coroutine_test( + "Camera profile should update for a changed Status Light AttributeList report", + function() + update_device_profile() + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.CameraAvStreamManagement.attributes.AttributeList:build_test_report_data(mock_device, CAMERA_EP, { + uint32(clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID) + }) + }) + local expected_metadata = { + optional_component_capabilities = { + { "main", + { "videoCapture2", "cameraViewportSettings", "localMediaStorage", "audioRecording", "cameraPrivacyMode", + "imageControl", "hdr", "nightVision", "mechanicalPanTiltZoom", "videoStreamSettings", "zoneManagement", + "webrtc", "motionSensor", "sounds", } + }, + { "statusLed", + { "switch" } -- only switch capability remains + }, + { "speaker", + { "audioMute", "audioVolume" } + }, + { "microphone", + { "audioMute", "audioVolume" } + }, + { "doorbell", + { "button" } + } + }, + profile = "camera" + } + mock_device:expect_metadata_update(expected_metadata) + test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, DOORBELL_EP)}) + test.socket.capability:__expect_send(mock_device:generate_test_message("doorbell", capabilities.button.button.pushed({state_change = false}))) + end +) + +-- run the tests +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_light_fan.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_light_fan.lua index 078b99c7fe..676a376726 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_light_fan.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_light_fan.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.matter.generated.zap_clusters" @@ -26,12 +15,16 @@ local mock_device_ep1 = 1 local mock_device_ep2 = 2 local mock_device = test.mock_device.build_test_matter_device({ - label = "Matter Switch", - profile = t_utils.get_profile_definition("light-color-level-fan.yml"), + label = "Matter Fan Light", + profile = t_utils.get_profile_definition("fan-modular.yml", {}), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, }, + matter_version = { + software = 1, + hardware = 1, + }, endpoints = { { endpoint_id = 0, @@ -45,7 +38,7 @@ local mock_device = test.mock_device.build_test_matter_device({ { endpoint_id = mock_device_ep1, clusters = { - {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", feature_map = 0}, {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2}, {cluster_id = clusters.ColorControl.ID, cluster_type = "BOTH", feature_map = 30}, }, @@ -83,23 +76,46 @@ local CLUSTER_SUBSCRIBE_LIST ={ clusters.FanControl.attributes.PercentCurrent, } +local mock_child = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("light-color-level.yml"), + device_network_id = string.format("%s:%d", mock_device.id, 4), + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%d", mock_device_ep1) +}) + local function test_init() test.disable_startup_messages() test.mock_device.add_test_device(mock_device) + test.mock_device.add_test_device(mock_child) local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device) for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end end test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) -- since all fan capabilities are optional, nothing is initially subscribed to test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) - mock_device:expect_metadata_update({ profile = "light-color-level-fan" }) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, mock_device_ep1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.ColorControl.attributes.Options:write(mock_device, mock_device_ep1, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + mock_device:expect_device_create({ + type = "EDGE_CHILD", + label = "Matter Fan Light 1", + profile = "light-color-level", + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%d", mock_device_ep1) + }) + mock_device:expect_metadata_update({ profile = "fan-modular", optional_component_capabilities = {{"main", {"fanSpeedPercent", "fanMode"}}} }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + + local updated_device_profile = t_utils.get_profile_definition("fan-modular.yml", + {enabled_optional_capabilities = {{"main", {"fanSpeedPercent", "fanMode"}}}} + ) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ profile = updated_device_profile })) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) end test.set_test_init_function(test_init) @@ -108,7 +124,7 @@ test.register_coroutine_test( "Switch capability should send the appropriate commands", function() test.socket.capability:__queue_receive( { - mock_device.id, + mock_child.id, { capability = "switch", component = "main", command = "on", args = { } } } ) @@ -116,14 +132,14 @@ test.register_coroutine_test( test.socket.devices:__expect_send( { "register_native_capability_cmd_handler", - { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "on" } + { device_uuid = mock_child.id, capability_id = "switch", capability_cmd_id = "on" } } ) end test.socket.matter:__expect_send( { mock_device.id, - clusters.OnOff.server.commands.On(mock_device, 1) + clusters.OnOff.server.commands.On(mock_device, mock_device_ep1) } ) test.socket.matter:__queue_receive( @@ -138,9 +154,8 @@ test.register_coroutine_test( { device_uuid = mock_device.id, capability_id = "switch", capability_attr_id = "switch" } } ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( + mock_child:generate_test_message( "main", capabilities.switch.switch.on() ) ) @@ -154,7 +169,7 @@ test.register_message_test( channel = "capability", direction = "receive", message = { - mock_device.id, + mock_child.id, { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {1800} } } }, @@ -185,7 +200,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) + message = mock_child:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) }, } ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua index d8b0116101..301fb36cf0 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" @@ -10,13 +13,16 @@ local button_attr = capabilities.button.button -- Mock a 5-button device using endpoints non-consecutive endpoints local mock_device = test.mock_device.build_test_matter_device( { - profile = t_utils.get_profile_definition("5-button-battery.yml"), -- on a real device we would switch to this, rather than fingerprint to it + profile = t_utils.get_profile_definition("5-button-battery.yml"), manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000}, + matter_version = {hardware = 1, sofrware = 1}, endpoints = { { endpoint_id = 0, clusters = {}, - device_types = {} + device_types = { + {device_type_id = 0x0016, device_type_revision = 1}, -- RootNode + } }, { endpoint_id = 10, @@ -87,8 +93,7 @@ local mock_device = test.mock_device.build_test_matter_device( } }, }, -} -) +}) -- add device for each mock device local CLUSTER_SUBSCRIBE_LIST ={ diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_motion.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_motion.lua new file mode 100644 index 0000000000..dca5f20645 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_motion.lua @@ -0,0 +1,769 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local utils = require "st.utils" +local dkjson = require "dkjson" + +local clusters = require "st.matter.generated.zap_clusters" +local button_attr = capabilities.button.button + +local mock_device = test.mock_device.build_test_matter_device( + { + profile = t_utils.get_profile_definition("6-button-motion.yml"), -- on a real device we would switch to this, rather than fingerprint to it + manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000}, + matter_version = {hardware = 1, software = 1}, + endpoints = { + { + endpoint_id = 0, + clusters = {}, + device_types = {} + }, + { + endpoint_id = 10, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + cluster_type = "SERVER" + }, + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 20, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH | clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_RELEASE, + cluster_type = "SERVER" + }, + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 30, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH | clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_LONG_PRESS, + cluster_type = "SERVER" + }, + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 40, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH | clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_LONG_PRESS, + cluster_type = "SERVER" + }, + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 50, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH | clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS, + cluster_type = "SERVER" + }, + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 60, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_LONG_PRESS, + cluster_type = "SERVER" + }, + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 70, + clusters = { + {cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0107, device_type_revision = 1} -- OccupancySensor + } + }, + }, +} +) + +-- add device for each mock device +local CLUSTER_SUBSCRIBE_LIST ={ + clusters.OccupancySensing.attributes.Occupancy, + clusters.Switch.server.events.InitialPress, + clusters.Switch.server.events.LongPress, + clusters.Switch.server.events.ShortRelease, + clusters.Switch.server.events.MultiPressComplete, +} + +local function expect_configure_buttons() + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", button_attr.pushed({state_change = false}))) + + test.socket.capability:__expect_send(mock_device:generate_test_message("button2", capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("button2", button_attr.pushed({state_change = false}))) + + test.socket.capability:__expect_send(mock_device:generate_test_message("button3", capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = false}))) + + test.socket.capability:__expect_send(mock_device:generate_test_message("button4", capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("button4", button_attr.pushed({state_change = false}))) + + test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, 50)}) + test.socket.capability:__expect_send(mock_device:generate_test_message("button5", button_attr.pushed({state_change = false}))) + + test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, 60)}) + test.socket.capability:__expect_send(mock_device:generate_test_message("button6", button_attr.pushed({state_change = false}))) +end + +-- All messages queued and expectations set are done before the driver is actually run +local function test_init() + -- we dont want the integration test framework to generate init/doConfigure, we are doing that here + -- so we can set the proper expectations on those events. + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device) -- make sure the cache is populated + + -- added sets a bunch of fields on the device, and calls init + local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device) + for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end + end + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + -- init results in subscription interaction + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + + --doConfigure sets the provisioning state to provisioned + mock_device:expect_metadata_update({ profile = "6-button-motion" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + expect_configure_buttons() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + + -- simulate the profile change update taking affect and the device info changing + local device_info_copy = utils.deep_copy(mock_device.raw_st_data) + device_info_copy.profile.id = "6-buttons-motion" + local device_info_json = dkjson.encode(device_info_copy) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json }) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + expect_configure_buttons() +end +test.set_test_init_function(test_init) + +test.register_message_test( + "Handle single press sequence, no hold", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 10, {new_position = 1} --move to position 1? + ), + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) --should send initial press + } +} +) + +test.register_message_test( + "Handle single press sequence for short release-supported button", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 20, {new_position = 1} --move to position 1? + ), + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report( + mock_device, 20, {previous_position = 0} --move to position 1? + ), + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("button2", button_attr.pushed({state_change = true})) --should send initial press + } +} +) + +test.register_coroutine_test( + "Handle single press sequence for emulated hold on short-release-only button", + function () + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 20, {new_position = 1} + ) + }) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report( + mock_device, 20, {previous_position = 0} + ) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("button2", button_attr.held({state_change = true}))) + end +) + +test.register_coroutine_test( + "Handle single press sequence for a long hold on long-release-capable button", -- only a long press event should generate a held event + function () + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 30, {new_position = 1} + ) + }) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report( + mock_device, 30, {previous_position = 0} + ) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = true}))) + end +) + +test.register_coroutine_test( + "Handle single press sequence for a long hold on multi button", -- pushes should only be generated from multiPressComplete events + function () + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 50, {new_position = 1} + ) + }) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report( + mock_device, 50, {previous_position = 0} + ) + }) + end +) + +test.register_coroutine_test( + "Handle single press sequence for a multi press on multi button", + function () + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report( + mock_device, 60, {previous_position = 0} + ) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_device, 60, {new_position = 1, current_number_of_presses_counted = 2} + ) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_device, 60, {new_position = 0, total_number_of_presses_counted = 2, previous_position = 1} + ) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("button6", button_attr.double({state_change = true}))) + end +) + +test.register_coroutine_test( + "Handle long press sequence for a long hold on long-release-capable button", -- only a long press event should generate a held event + function () + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 40, {new_position = 1} + ) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + mock_device, 40, {new_position = 1} + ) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("button4", button_attr.held({state_change = true}))) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.LongRelease:build_test_event_report( + mock_device, 40, {previous_position = 0} + ) + }) + end +) + +test.register_coroutine_test( + "Occupancy reports should generate correct messages", + function () + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 70, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.active())) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 70, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) + end +) + +test.register_coroutine_test( + "Handle long press sequence for a long hold on multi button", + function () + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("button6", button_attr.held({state_change = true}))) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.LongRelease:build_test_event_report( + mock_device, 60, {previous_position = 0} + ) + }) + end +) + +test.register_message_test( + "Handle single press sequence, with hold", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 10, {new_position = 1} + ), + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) --should send initial press + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + mock_device, 10, {new_position = 1} + ), + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.held({state_change = true})) + } +} +) + +test.register_message_test( + "Handle release after short press", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 10, {new_position = 1} + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report( + mock_device, 10, {previous_position = 1} + ) + } + }, + { -- this is a double event because the test device in this test shouldn't support the above event + -- but we handle it anyway + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) + }, + } +) + +test.register_message_test( + "Handle release after long press", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 10, {new_position = 1} + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + mock_device, 10, {new_position = 1} + ), + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.held({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.LongRelease:build_test_event_report( + mock_device, 10, {previous_position = 1} + ) + } + }, + } +) + +test.register_message_test( + "Receiving a max press attribute of 2 should emit correct event", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.attributes.MultiPressMax:build_test_report_data( + mock_device, 10, 2 + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.button.supportedButtonValues({"pushed", "double"}, {visibility = {displayed = false}})) + }, + } +) + +test.register_message_test( + "Receiving a max press attribute of 3 should emit correct event", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.attributes.MultiPressMax:build_test_report_data( + mock_device, 60, 3 + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("button6", + capabilities.button.supportedButtonValues({"pushed", "double", "held", "pushed_3x"}, {visibility = {displayed = false}})) + }, + } +) + +test.register_message_test( + "Receiving a max press attribute of greater than 6 should only emit up to pushed_6x", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.attributes.MultiPressMax:build_test_report_data( + mock_device, 10, 7 + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.button.supportedButtonValues({"pushed", "double", "pushed_3x", "pushed_4x", "pushed_5x", "pushed_6x"}, {visibility = {displayed = false}})) + }, + } +) + +test.register_message_test( + "Handle double press", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 10, {new_position = 1} + ) + } + }, + { -- again, on a device that reports that it supports double press, this event + -- will not be generated. See a multi-button test file for that case + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_device, 10, {new_position = 1, total_number_of_presses_counted = 2, previous_position = 0} + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.double({state_change = true})) + }, + +} +) + +test.register_message_test( + "Handle multi press for 4 times", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 10, {new_position = 1} + ) + } + }, + { -- again, on a device that reports that it supports double press, this event + -- will not be generated. See a multi-button test file for that case + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_device, 10, {new_position = 1, total_number_of_presses_counted = 4, previous_position=0} + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed_4x({state_change = true})) + }, + +} +) + +test.register_message_test( + "Receiving a max press attribute of 2 should emit correct event", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.attributes.MultiPressMax:build_test_report_data( + mock_device, 50, 2 + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("button5", + capabilities.button.supportedButtonValues({"pushed", "double"}, {visibility = {displayed = false}})) + }, + } +) + +test.register_message_test( + "Handle a long press including MultiPressComplete", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("button6", button_attr.held({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_device, 60, {new_position = 0, total_number_of_presses_counted = 1, previous_position=0} + ) + } + } + -- no double event +} +) + +test.register_message_test( + "Handle long press followed by single press", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("button6", button_attr.held({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_device, 60, {new_position = 0, total_number_of_presses_counted = 1, previous_position=0} + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("button6", button_attr.pushed({state_change = true})) + } + } +) +-- run the tests +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua index af7511100e..d412c2b35e 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua @@ -1,8 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" -local utils = require "st.utils" -local dkjson = require "dkjson" local clusters = require "st.matter.generated.zap_clusters" @@ -25,6 +26,7 @@ local mock_device = test.mock_device.build_test_matter_device({ vendor_id = 0x0000, product_id = 0x0000, }, + matter_version = {hardware = 1, software = 1}, endpoints = { { endpoint_id = 0, @@ -100,11 +102,12 @@ local mock_device = test.mock_device.build_test_matter_device({ local mock_device_mcd_unsupported_switch_device_type = test.mock_device.build_test_matter_device({ label = "Matter Switch", - profile = t_utils.get_profile_definition("matter-thing.yml"), + profile = t_utils.get_profile_definition("button.yml"), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, }, + matter_version = {hardware = 1, software = 1}, endpoints = { { endpoint_id = 0, @@ -163,7 +166,18 @@ local child_data = { local mock_child = test.mock_device.build_test_child_device(child_data) -- add device for each mock device -local CLUSTER_SUBSCRIBE_LIST ={ +local CLUSTER_SUBSCRIBE_LIST_NO_CHILD ={ + clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel, + clusters.Switch.server.events.InitialPress, + clusters.Switch.server.events.LongPress, + clusters.Switch.server.events.ShortRelease, + clusters.Switch.server.events.MultiPressComplete, +} + +local CLUSTER_SUBSCRIBE_LIST_WITH_CHILD ={ clusters.OnOff.attributes.OnOff, clusters.LevelControl.attributes.CurrentLevel, clusters.LevelControl.attributes.MaxLevel, @@ -175,6 +189,7 @@ local CLUSTER_SUBSCRIBE_LIST ={ clusters.ColorControl.attributes.CurrentSaturation, clusters.ColorControl.attributes.CurrentX, clusters.ColorControl.attributes.CurrentY, + clusters.ColorControl.attributes.ColorMode, clusters.Switch.server.events.InitialPress, clusters.Switch.server.events.LongPress, clusters.Switch.server.events.ShortRelease, @@ -192,27 +207,24 @@ local function expect_configure_buttons() test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = false}))) end --- All messages queued and expectations set are done before the driver is actually run local function test_init() - -- we dont want the integration test framework to generate init/doConfigure, we are doing that here - -- so we can set the proper expectations on those events. test.disable_startup_messages() test.mock_device.add_test_device(mock_device) -- make sure the cache is populated - test.mock_device.add_test_device(mock_child) -- added sets a bunch of fields on the device, and calls init - local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device) - for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do + local subscribe_request = CLUSTER_SUBSCRIBE_LIST_NO_CHILD[1]:subscribe(mock_device) + for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST_NO_CHILD) do if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - -- init results in subscription interaction test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) - --doConfigure sets the provisioning state to provisioned + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, mock_device_ep1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, mock_device_ep5, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.ColorControl.attributes.Options:write(mock_device, mock_device_ep5, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) mock_device:expect_metadata_update({ profile = "light-level-3-button" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) mock_device:expect_device_create({ @@ -224,20 +236,6 @@ local function test_init() }) expect_configure_buttons() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) - - -- simulate the profile change update taking affect and the device info changing - local device_info_copy = utils.deep_copy(mock_device.raw_st_data) - device_info_copy.profile.id = "5-buttons-battery" - local device_info_json = dkjson.encode(device_info_copy) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json }) - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - expect_configure_buttons() - - test.socket.matter:__expect_send({mock_device.id, clusters.OnOff.attributes.OnOff:read(mock_device)}) - test.socket.device_lifecycle:__queue_receive({ mock_child.id, "added" }) - test.socket.device_lifecycle:__queue_receive({ mock_child.id, "init" }) - mock_child:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - test.socket.device_lifecycle:__queue_receive({ mock_child.id, "doConfigure" }) end -- All messages queued and expectations set are done before the driver is actually run @@ -357,94 +355,90 @@ test.register_coroutine_test( end ) -test.register_message_test( +test.register_coroutine_test( "Switch child device: Set color temperature should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_child.id, - { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {1800} } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, mock_device_ep5, 556, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.server.commands.MoveToColorTemperature:build_test_command_response(mock_device, mock_device_ep5) - } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, mock_device_ep5, 556) - } - }, - { - channel = "capability", - direction = "send", - message = mock_child:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) - }, - } + function() + test.mock_device.add_test_device(mock_child) + test.wait_for_events() + test.socket.capability:__queue_receive({ + mock_child.id, + { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {1800} } + }) + test.socket.matter:__expect_send({ + mock_device.id, + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, mock_device_ep5, 556, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ColorControl.server.commands.MoveToColorTemperature:build_test_command_response(mock_device, mock_device_ep5) + }) + test.wait_for_events() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, mock_device_ep5, 556) + }) + test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800))) + end ) test.register_coroutine_test( "Test MCD configuration not including switch for unsupported switch device type, create child device instead", function() + local unsup_mock_device = mock_device_mcd_unsupported_switch_device_type -- added sets a bunch of fields on the device, and calls init - local cluster_subscribe_list = { - clusters.OnOff.attributes.OnOff, + local CLUSTER_SUBSCRIBE_LIST = { clusters.Switch.server.events.InitialPress, clusters.Switch.server.events.LongPress, clusters.Switch.server.events.ShortRelease, clusters.Switch.server.events.MultiPressComplete, } - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_mcd_unsupported_switch_device_type) - for i, cluster in ipairs(cluster_subscribe_list) do - if i > 1 then - subscribe_request:merge(cluster:subscribe(mock_device_mcd_unsupported_switch_device_type)) - end + local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(unsup_mock_device) + for _, cluster in ipairs(CLUSTER_SUBSCRIBE_LIST) do + subscribe_request:merge(cluster:subscribe(unsup_mock_device)) end - test.socket.matter:__expect_send({mock_device_mcd_unsupported_switch_device_type.id, subscribe_request}) - test.socket.device_lifecycle:__queue_receive({ mock_device_mcd_unsupported_switch_device_type.id, "added" }) - test.wait_for_events() + test.socket.device_lifecycle:__queue_receive({ unsup_mock_device.id, "added" }) + test.socket.matter:__expect_send({unsup_mock_device.id, subscribe_request}) - -- init results in subscription interaction - test.socket.matter:__expect_send({mock_device_mcd_unsupported_switch_device_type.id, subscribe_request}) - test.socket.device_lifecycle:__queue_receive({ mock_device_mcd_unsupported_switch_device_type.id, "init" }) - test.wait_for_events() + test.socket.device_lifecycle:__queue_receive({ unsup_mock_device.id, "init" }) + test.socket.matter:__expect_send({unsup_mock_device.id, subscribe_request}) - -- doConfigure sets the provisioning state to provisioned - mock_device_mcd_unsupported_switch_device_type:expect_metadata_update({ profile = "2-button" }) - mock_device_mcd_unsupported_switch_device_type:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - mock_device_mcd_unsupported_switch_device_type:expect_device_create({ + test.socket.device_lifecycle:__queue_receive({ unsup_mock_device.id, "doConfigure" }) + unsup_mock_device:expect_device_create({ type = "EDGE_CHILD", label = "Matter Switch 1", profile = "switch-binary", - parent_device_id = mock_device_mcd_unsupported_switch_device_type.id, + parent_device_id = unsup_mock_device.id, parent_assigned_child_key = string.format("%d", 7) }) - test.socket.device_lifecycle:__queue_receive({ mock_device_mcd_unsupported_switch_device_type.id, "doConfigure" }) + test.socket.capability:__expect_send(unsup_mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(unsup_mock_device:generate_test_message("main", button_attr.pushed({state_change = false}))) + unsup_mock_device:expect_metadata_update({ profile = "2-button" }) + unsup_mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() - -- simulate the profile change update taking affect and the device info changing - local device_info_copy = utils.deep_copy(mock_device_mcd_unsupported_switch_device_type.raw_st_data) - device_info_copy.profile.id = "5-buttons-battery" - local device_info_json = dkjson.encode(device_info_copy) - test.socket.device_lifecycle:__queue_receive({ mock_device_mcd_unsupported_switch_device_type.id, "infoChanged", device_info_json }) - test.socket.matter:__expect_send({mock_device_mcd_unsupported_switch_device_type.id, subscribe_request}) + local updated_device_profile = t_utils.get_profile_definition("2-button.yml") + test.socket.device_lifecycle:__queue_receive(unsup_mock_device:generate_info_changed({ profile = updated_device_profile })) + + local CLUSTER_SUBSCRIBE_LIST = { + clusters.Switch.server.events.InitialPress, + clusters.Switch.server.events.LongPress, + clusters.Switch.server.events.ShortRelease, + clusters.Switch.server.events.MultiPressComplete, + } + local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(unsup_mock_device) + for i, cluster in ipairs(CLUSTER_SUBSCRIBE_LIST) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(unsup_mock_device)) + end + end + test.socket.matter:__expect_send({unsup_mock_device.id, subscribe_request}) + + test.socket.capability:__expect_send(unsup_mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(unsup_mock_device:generate_test_message("main", button_attr.pushed({state_change = false}))) + + test.socket.capability:__expect_send(unsup_mock_device:generate_test_message("button2", capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(unsup_mock_device:generate_test_message("button2", button_attr.pushed({state_change = false}))) end, { test_init = test_init_mcd_unsupported_switch_device_type } ) @@ -452,16 +446,60 @@ test.register_coroutine_test( test.register_coroutine_test( "Test driver switched event", function() + test.mock_device.add_test_device(mock_child) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + local subscribe_request = CLUSTER_SUBSCRIBE_LIST_WITH_CHILD[1]:subscribe(mock_device) + for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST_WITH_CHILD) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end + end + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "driverSwitched" }) + mock_child:expect_metadata_update({ profile = "light-color-level" }) mock_device:expect_metadata_update({ profile = "light-level-3-button" }) expect_configure_buttons() - mock_device:expect_device_create({ - type = "EDGE_CHILD", - label = "Matter Switch 2", - profile = "light-color-level", - parent_device_id = mock_device.id, - parent_assigned_child_key = string.format("%d", mock_device_ep5) - }) + end +) + +test.register_coroutine_test( + "Test info changed event with parent device profile update", + function() + local subscribe_request = CLUSTER_SUBSCRIBE_LIST_NO_CHILD[1]:subscribe(mock_device) + for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST_NO_CHILD) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end + end + local updated_device_profile = t_utils.get_profile_definition("light-level-3-button.yml") + updated_device_profile.id = "updated device profile id" + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ profile = updated_device_profile })) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + expect_configure_buttons() + end +) + +test.register_coroutine_test( + "Test info changed event with matter_version update", + function() + test.mock_device.add_test_device(mock_child) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ matter_version = { hardware = 1, software = 2 } })) -- bump sw to 2 + mock_child:expect_metadata_update({ profile = "light-color-level" }) + mock_device:expect_metadata_update({ profile = "light-level-3-button" }) + expect_configure_buttons() + end +) + +test.register_coroutine_test( + "Test child device initialization, and that subscriptions are initialized correctly", + function () + test.mock_device.add_test_device(mock_child) + test.socket.matter:__expect_send({mock_device.id, clusters.OnOff.attributes.OnOff:read(mock_device)}) + test.socket.device_lifecycle:__queue_receive({ mock_child.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_child.id, "init" }) + local subscribe_request = CLUSTER_SUBSCRIBE_LIST_WITH_CHILD[1]:subscribe(mock_device) + for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST_WITH_CHILD) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end + end + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + mock_child:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.device_lifecycle:__queue_receive({ mock_child.id, "doConfigure" }) end ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua index 84ccc9e478..b57c416434 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -1,20 +1,10 @@ --- Copyright 2022 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. +-- Copyright © 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" +local st_utils = require "st.utils" local clusters = require "st.matter.clusters" local TRANSITION_TIME = 0 @@ -77,6 +67,52 @@ local mock_device_no_hue_sat = test.mock_device.build_test_matter_device({ } }) +local mock_device_color_temp = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("light-level-colorTemperature.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + endpoints = { + { + endpoint_id = 1, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.ColorControl.ID, cluster_type = "BOTH", feature_map = 30}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER"} + }, + device_types = { + {device_type_id = 0x0100, device_type_revision = 1}, -- On/Off Light + {device_type_id = 0x010C, device_type_revision = 1} -- Color Temperature Light + } + } + } +}) + +local mock_device_extended_color = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("light-color-level.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + endpoints = { + { + endpoint_id = 1, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.ColorControl.ID, cluster_type = "BOTH", feature_map = 30}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2} + }, + device_types = { + {device_type_id = 0x0100, device_type_revision = 1}, -- On/Off Light + {device_type_id = 0x0101, device_type_revision = 1}, -- Dimmable Light + {device_type_id = 0x010C, device_type_revision = 1}, -- Color Temperature Light + {device_type_id = 0x010D, device_type_revision = 1}, -- Extended Color Light + } + } + } +}) + local cluster_subscribe_list = { clusters.OnOff.attributes.OnOff, clusters.LevelControl.attributes.CurrentLevel, @@ -90,6 +126,7 @@ local cluster_subscribe_list = { clusters.ColorControl.attributes.ColorTemperatureMireds, clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, + clusters.ColorControl.attributes.ColorMode, } local function set_color_mode(device, endpoint, color_mode) @@ -146,6 +183,84 @@ local function test_init_no_hue_sat() set_color_mode(mock_device_no_hue_sat, 1, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) end + +local cluster_subscribe_list_color_temp = { + clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel, + clusters.ColorControl.attributes.ColorTemperatureMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds +} + +local function test_init_color_temp() + test.mock_device.add_test_device(mock_device_color_temp) + local subscribe_request = cluster_subscribe_list_color_temp[1]:subscribe(mock_device_color_temp) + for i, cluster in ipairs(cluster_subscribe_list_color_temp) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device_color_temp)) + end + end + + test.socket.device_lifecycle:__queue_receive({ mock_device_color_temp.id, "added" }) + test.socket.matter:__expect_send({mock_device_color_temp.id, subscribe_request}) + + test.socket.device_lifecycle:__queue_receive({ mock_device_color_temp.id, "init" }) + test.socket.matter:__expect_send({mock_device_color_temp.id, subscribe_request}) + + test.socket.device_lifecycle:__queue_receive({ mock_device_color_temp.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_color_temp.id, + clusters.LevelControl.attributes.Options:write(mock_device_color_temp, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + test.socket.matter:__expect_send({ + mock_device_color_temp.id, + clusters.ColorControl.attributes.Options:write(mock_device_color_temp, 1, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + mock_device_color_temp:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.matter:__expect_send({mock_device_color_temp.id, subscribe_request}) +end + +local function test_init_extended_color() + test.mock_device.add_test_device(mock_device_extended_color) + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_extended_color) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device_extended_color)) + end + end + test.socket.matter:__expect_send({mock_device_extended_color.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device_extended_color.id, "added" }) + + test.socket.device_lifecycle:__queue_receive({ mock_device_extended_color.id, "init" }) + test.socket.matter:__expect_send({mock_device_extended_color.id, subscribe_request}) + + test.socket.device_lifecycle:__queue_receive({ mock_device_extended_color.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_extended_color.id, + clusters.LevelControl.attributes.Options:write(mock_device_extended_color, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + test.socket.matter:__expect_send({ + mock_device_extended_color.id, + clusters.ColorControl.attributes.Options:write(mock_device_extended_color, 1, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + mock_device_extended_color:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.matter:__expect_send({mock_device_extended_color.id, subscribe_request}) +end + +test.register_message_test( + "Test that Color Temperature Light device does not switch profiles", + {}, + { test_init = test_init_color_temp } +) + +test.register_message_test( + "Test that Extended Color Light device does not switch profiles", + {}, + { test_init = test_init_extended_color } +) + test.register_message_test( "On command should send the appropriate commands", { @@ -230,7 +345,7 @@ test.register_message_test( direction = "send", message = { mock_device.id, - clusters.LevelControl.server.commands.MoveToLevelWithOnOff(mock_device, 1, math.floor(20/100.0 * 254), 20, 0 ,0) + clusters.LevelControl.server.commands.MoveToLevelWithOnOff(mock_device, 1, st_utils.round(20/100.0 * 254), 20, 0 ,0) } }, { @@ -301,7 +416,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_device:generate_test_message("main", capabilities.switchLevel.level(math.floor((50 / 254.0 * 100) + 0.5))) + message = mock_device:generate_test_message("main", capabilities.switchLevel.level(st_utils.round((50 / 254.0 * 100) + 0.5))) }, { channel = "devices", @@ -411,6 +526,14 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.colorControl.hue(50)) }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "hue" } + } + }, { channel = "matter", direction = "receive", @@ -423,7 +546,15 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_device:generate_test_message("main", capabilities.colorControl.saturation(50)) - } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "saturation" } + } + }, } ) @@ -477,6 +608,14 @@ test.register_message_test( direction = "send", message = mock_device:generate_test_message("main", capabilities.colorControl.hue(100)) }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "hue" } + } + }, { channel = "matter", direction = "receive", @@ -489,7 +628,15 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_device:generate_test_message("main", capabilities.colorControl.saturation(100)) - } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "saturation" } + } + }, } ) @@ -970,6 +1117,12 @@ test.register_coroutine_test( "main", capabilities.colorControl.hue(100) ) ) + test.socket.devices:__expect_send( + { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "hue" } + } + ) test.socket.matter:__queue_receive( { mock_device.id, @@ -981,6 +1134,12 @@ test.register_coroutine_test( "main", capabilities.colorControl.saturation(100) ) ) + test.socket.devices:__expect_send( + { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "colorControl", capability_attr_id = "saturation" } + } + ) end, { test_init = test_init_x_y_color_mode } ) @@ -1027,4 +1186,19 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Refresh necessary attributes", + function() + test.socket.capability:__queue_receive( + {mock_device.id, {capability = "refresh", component = "main", command = "refresh", args = {}}} + ) + local read_request = cluster_subscribe_list[1]:read(mock_device) + for i, attr in ipairs(cluster_subscribe_list) do + if i > 1 then read_request:merge(attr:read(mock_device)) end + end + test.socket.matter:__expect_send({mock_device.id, read_request}) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua index 0046244bcd..5513c915b7 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" @@ -24,6 +13,10 @@ local mock_device_onoff = test.mock_device.build_test_matter_device({ vendor_id = 0x0000, product_id = 0x0000, }, + matter_version = { + hardware = 1, + software = 1, + }, endpoints = { { endpoint_id = 0, @@ -103,6 +96,35 @@ local mock_device_dimmer = test.mock_device.build_test_matter_device({ } }) +local mock_device_switch_vendor_override = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("switch-binary.yml"), + manufacturer_info = { + vendor_id = 0x109B, + product_id = 0x1001, + }, + 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.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, + }, + device_types = { + {device_type_id = 0x010A, device_type_revision = 1} -- OnOff PlugIn Unit + } + } + } +}) + + local mock_device_color_dimmer = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("matter-thing.yml"), manufacturer_info = { @@ -154,7 +176,7 @@ local mock_device_mounted_on_off_control = test.mock_device.build_test_matter_de endpoint_id = 7, clusters = { {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, - {cluster_id = clusters.LevelControl.ID, cluster_type = "CLIENT", feature_map = 2}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2}, }, device_types = { @@ -184,7 +206,7 @@ local mock_device_mounted_dimmable_load_control = test.mock_device.build_test_ma endpoint_id = 7, clusters = { {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, - {cluster_id = clusters.LevelControl.ID, cluster_type = "CLIENT", feature_map = 2}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2}, }, device_types = { @@ -396,12 +418,7 @@ local mock_device_light_level_motion = test.mock_device.build_test_matter_device { endpoint_id = 1, clusters = { - { - cluster_id = clusters.OnOff.ID, - cluster_type = "SERVER", - cluster_revision = 1, - feature_map = 0, --u32 bitmap - }, + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER"} }, device_types = { @@ -422,15 +439,13 @@ local mock_device_light_level_motion = test.mock_device.build_test_matter_device local function test_init_parent_child_switch_types() test.mock_device.add_test_device(mock_device_parent_child_switch_types) - local subscribe_request = clusters.OnOff.attributes.OnOff:subscribe(mock_device_parent_child_switch_types) - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_switch_types.id, "added" }) - test.socket.matter:__expect_send({mock_device_parent_child_switch_types.id, subscribe_request}) - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_switch_types.id, "init" }) - test.socket.matter:__expect_send({mock_device_parent_child_switch_types.id, subscribe_request}) - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_switch_types.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_parent_child_switch_types.id, + clusters.LevelControl.attributes.Options:write(mock_device_parent_child_switch_types, 7, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) mock_device_parent_child_switch_types:expect_metadata_update({ profile = "switch-level" }) mock_device_parent_child_switch_types:expect_metadata_update({ provisioning_state = "PROVISIONED" }) @@ -454,17 +469,18 @@ end local function test_init_onoff_client() test.mock_device.add_test_device(mock_device_onoff_client) + test.socket.device_lifecycle:__queue_receive({ mock_device_onoff_client.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_onoff_client.id, "init" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_onoff_client.id, "doConfigure" }) + mock_device_onoff_client:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end local function test_init_parent_client_child_server() test.mock_device.add_test_device(mock_device_parent_client_child_server) - local subscribe_request = clusters.OnOff.attributes.OnOff:subscribe(mock_device_parent_client_child_server) test.socket.device_lifecycle:__queue_receive({ mock_device_parent_client_child_server.id, "added" }) - test.socket.matter:__expect_send({mock_device_parent_client_child_server.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_parent_client_child_server.id, "init" }) - test.socket.matter:__expect_send({mock_device_parent_client_child_server.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_parent_client_child_server.id, "doConfigure" }) mock_device_parent_client_child_server:expect_metadata_update({ profile = "switch-binary" }) @@ -473,7 +489,13 @@ end local function test_init_dimmer() test.mock_device.add_test_device(mock_device_dimmer) + test.socket.device_lifecycle:__queue_receive({ mock_device_dimmer.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_dimmer.id, "init" }) test.socket.device_lifecycle:__queue_receive({ mock_device_dimmer.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_dimmer.id, + clusters.LevelControl.attributes.Options:write(mock_device_dimmer, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) mock_device_dimmer:expect_metadata_update({ profile = "switch-level" }) mock_device_dimmer:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -487,6 +509,18 @@ local function test_init_color_dimmer() mock_device_color_dimmer:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end +local function test_init_switch_vendor_override() + test.mock_device.add_test_device(mock_device_switch_vendor_override) + local subscribe_request = clusters.OnOff.attributes.OnOff:subscribe(mock_device_switch_vendor_override) + test.socket.device_lifecycle:__queue_receive({ mock_device_switch_vendor_override.id, "added" }) + test.socket.matter:__expect_send({mock_device_switch_vendor_override.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device_switch_vendor_override.id, "init" }) + test.socket.matter:__expect_send({mock_device_switch_vendor_override.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device_switch_vendor_override.id, "doConfigure" }) + mock_device_switch_vendor_override:expect_metadata_update({ profile = "switch-binary" }) + mock_device_switch_vendor_override:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + local function test_init_mounted_on_off_control() test.mock_device.add_test_device(mock_device_mounted_on_off_control) local cluster_subscribe_list = { @@ -505,6 +539,11 @@ local function test_init_mounted_on_off_control() test.socket.matter:__expect_send({mock_device_mounted_on_off_control.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_mounted_on_off_control.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_mounted_on_off_control.id, + clusters.LevelControl.attributes.Options:write(mock_device_mounted_on_off_control, 7, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + mock_device_mounted_on_off_control:expect_metadata_update({ profile = "switch-binary" }) mock_device_mounted_on_off_control:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -512,6 +551,9 @@ local function test_init_mounted_dimmable_load_control() test.mock_device.add_test_device(mock_device_mounted_dimmable_load_control) local cluster_subscribe_list = { clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MinLevel, + clusters.LevelControl.attributes.MaxLevel, } local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_mounted_dimmable_load_control) for i, cluster in ipairs(cluster_subscribe_list) do @@ -526,6 +568,11 @@ local function test_init_mounted_dimmable_load_control() test.socket.matter:__expect_send({mock_device_mounted_dimmable_load_control.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_mounted_dimmable_load_control.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_mounted_dimmable_load_control.id, + clusters.LevelControl.attributes.Options:write(mock_device_mounted_dimmable_load_control, 7, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + mock_device_mounted_dimmable_load_control:expect_metadata_update({ profile = "switch-level" }) mock_device_mounted_dimmable_load_control:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -541,24 +588,9 @@ end local function test_init_parent_child_different_types() test.mock_device.add_test_device(mock_device_parent_child_different_types) local cluster_subscribe_list = { - clusters.OnOff.attributes.OnOff, - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel, - clusters.ColorControl.attributes.ColorTemperatureMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, - clusters.ColorControl.attributes.CurrentHue, - clusters.ColorControl.attributes.CurrentSaturation, - clusters.ColorControl.attributes.CurrentX, - clusters.ColorControl.attributes.CurrentY + clusters.OnOff.attributes.OnOff } local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_parent_child_different_types) - for i, cluster in ipairs(cluster_subscribe_list) do - if i > 1 then - subscribe_request:merge(cluster:subscribe(mock_device_parent_child_different_types)) - end - end test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_different_types.id, "added" }) test.socket.matter:__expect_send({mock_device_parent_child_different_types.id, subscribe_request}) @@ -566,6 +598,15 @@ local function test_init_parent_child_different_types() test.socket.matter:__expect_send({mock_device_parent_child_different_types.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_different_types.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_parent_child_different_types.id, + clusters.LevelControl.attributes.Options:write(mock_device_parent_child_different_types, 10, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + test.socket.matter:__expect_send({ + mock_device_parent_child_different_types.id, + clusters.ColorControl.attributes.Options:write(mock_device_parent_child_different_types, 10, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + mock_device_parent_child_different_types:expect_metadata_update({ profile = "switch-binary" }) mock_device_parent_child_different_types:expect_metadata_update({ provisioning_state = "PROVISIONED" }) mock_device_parent_child_different_types:expect_device_create({ @@ -583,6 +624,10 @@ local function test_init_parent_child_unsupported_device_type() test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_unsupported_device_type.id, "init" }) test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_unsupported_device_type.id, "doConfigure" }) mock_device_parent_child_unsupported_device_type:expect_metadata_update({ profile = "switch-binary" }) + test.socket.matter:__expect_send({ + mock_device_parent_child_unsupported_device_type.id, + clusters.LevelControl.attributes.Options:write(mock_device_parent_child_unsupported_device_type, 10, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) mock_device_parent_child_unsupported_device_type:expect_metadata_update({ provisioning_state = "PROVISIONED" }) mock_device_parent_child_unsupported_device_type:expect_device_create({ @@ -617,6 +662,11 @@ local function test_init_light_level_motion() test.socket.matter:__expect_send({mock_device_light_level_motion.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_light_level_motion.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_light_level_motion.id, + clusters.LevelControl.attributes.Options:write(mock_device_light_level_motion, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + mock_device_light_level_motion:expect_metadata_update({ profile = "light-level-motion" }) mock_device_light_level_motion:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -648,6 +698,13 @@ test.register_coroutine_test( { test_init = test_init_onoff_client } ) +test.register_coroutine_test( + "Test init for device with requiring the switch category as a vendor override", + function() + end, + { test_init = test_init_switch_vendor_override } +) + test.register_coroutine_test( "Test init for mounted onoff control parent cluster as server", function() @@ -704,4 +761,4 @@ test.register_coroutine_test( { test_init = test_init_light_level_motion } ) -test.run_registered_tests() +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua index 16f0ea3103..6129dfd116 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright © 2024 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_mcd.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_mcd.lua index 8680a57740..75e88b5fce 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_mcd.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_mcd.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright © 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua index 21c9e1087d..3261360910 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright © 2024 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" @@ -34,6 +23,10 @@ local mock_device = test.mock_device.build_test_matter_device({ vendor_id = 0x0000, product_id = 0x0000, }, + matter_version = { + hardware = 1, + software = 1, + }, endpoints = { { endpoint_id = 0, @@ -173,7 +166,8 @@ local function test_init() clusters.ColorControl.attributes.CurrentHue, clusters.ColorControl.attributes.CurrentSaturation, clusters.ColorControl.attributes.CurrentX, - clusters.ColorControl.attributes.CurrentY + clusters.ColorControl.attributes.CurrentY, + clusters.ColorControl.attributes.ColorMode, } local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) for i, cluster in ipairs(cluster_subscribe_list) do @@ -189,6 +183,10 @@ local function test_init() test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, child1_ep, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, child2_ep, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.ColorControl.attributes.Options:write(mock_device, child2_ep, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + mock_device:expect_metadata_update({ profile = "light-binary" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) for _, child in pairs(mock_children) do @@ -232,7 +230,9 @@ for i, endpoint in ipairs(mock_device_parent_child_endpoints_non_sequential.endp end local function test_init_parent_child_endpoints_non_sequential() - test.mock_device.add_test_device(mock_device_parent_child_endpoints_non_sequential) + local unsup_mock_device = mock_device_parent_child_endpoints_non_sequential + + test.mock_device.add_test_device(unsup_mock_device) local cluster_subscribe_list = { clusters.OnOff.attributes.OnOff, clusters.LevelControl.attributes.CurrentLevel, @@ -244,50 +244,56 @@ local function test_init_parent_child_endpoints_non_sequential() clusters.ColorControl.attributes.CurrentHue, clusters.ColorControl.attributes.CurrentSaturation, clusters.ColorControl.attributes.CurrentX, - clusters.ColorControl.attributes.CurrentY + clusters.ColorControl.attributes.CurrentY, + clusters.ColorControl.attributes.ColorMode, } - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_parent_child_endpoints_non_sequential) + local subscribe_request = cluster_subscribe_list[1]:subscribe(unsup_mock_device) for i, cluster in ipairs(cluster_subscribe_list) do if i > 1 then - subscribe_request:merge(cluster:subscribe(mock_device_parent_child_endpoints_non_sequential)) + subscribe_request:merge(cluster:subscribe(unsup_mock_device)) end end - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_endpoints_non_sequential.id, "added" }) - test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ unsup_mock_device.id, "added" }) + test.socket.matter:__expect_send({unsup_mock_device.id, subscribe_request}) - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_endpoints_non_sequential.id, "init" }) - test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ unsup_mock_device.id, "init" }) + test.socket.matter:__expect_send({unsup_mock_device.id, subscribe_request}) - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_endpoints_non_sequential.id, "doConfigure" }) - mock_device_parent_child_endpoints_non_sequential:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.device_lifecycle:__queue_receive({ unsup_mock_device.id, "doConfigure" }) + test.socket.matter:__expect_send({unsup_mock_device.id, clusters.LevelControl.attributes.Options:write(unsup_mock_device, child1_ep_non_sequential, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({unsup_mock_device.id, clusters.LevelControl.attributes.Options:write(unsup_mock_device, child2_ep_non_sequential, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({unsup_mock_device.id, clusters.ColorControl.attributes.Options:write(unsup_mock_device, child2_ep_non_sequential, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + + unsup_mock_device:expect_metadata_update({ profile = "switch-binary" }) + unsup_mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) for _, child in pairs(mock_children_non_sequential) do test.mock_device.add_test_device(child) end - mock_device_parent_child_endpoints_non_sequential:expect_device_create({ + unsup_mock_device:expect_device_create({ type = "EDGE_CHILD", label = "Matter Switch 2", profile = "light-color-level", - parent_device_id = mock_device_parent_child_endpoints_non_sequential.id, + parent_device_id = unsup_mock_device.id, parent_assigned_child_key = string.format("%d", child2_ep_non_sequential) }) -- switch-binary will be selected as an overridden child device profile - mock_device_parent_child_endpoints_non_sequential:expect_device_create({ + unsup_mock_device:expect_device_create({ type = "EDGE_CHILD", label = "Matter Switch 3", profile = "switch-binary", - parent_device_id = mock_device_parent_child_endpoints_non_sequential.id, + parent_device_id = unsup_mock_device.id, parent_assigned_child_key = string.format("%d", child3_ep_non_sequential) }) - mock_device_parent_child_endpoints_non_sequential:expect_device_create({ + unsup_mock_device:expect_device_create({ type = "EDGE_CHILD", label = "Matter Switch 4", profile = "light-level", - parent_device_id = mock_device_parent_child_endpoints_non_sequential.id, + parent_device_id = unsup_mock_device.id, parent_assigned_child_key = string.format("%d", child1_ep_non_sequential) }) end @@ -687,4 +693,14 @@ test.register_coroutine_test( { test_init = test_init_parent_child_endpoints_non_sequential } ) -test.run_registered_tests() +test.register_coroutine_test( + "Test info changed event with matter_version update", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ matter_version = { hardware = 1, software = 2 } })) -- bump to 2 + mock_children[child1_ep]:expect_metadata_update({ profile = "light-level" }) + mock_children[child2_ep]:expect_metadata_update({ profile = "light-color-level" }) + mock_device:expect_metadata_update({ profile = "light-binary" }) + end +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_plugs.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_plugs.lua index 03c898b7f5..92df754303 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_plugs.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_plugs.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright © 2023 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" @@ -19,15 +8,17 @@ local clusters = require "st.matter.clusters" test.disable_startup_messages() -local child_profile = t_utils.get_profile_definition("plug-binary.yml") -local child_profile_override = t_utils.get_profile_definition("switch-binary.yml") +local TRANSITION_TIME = 0 +local OPTIONS_MASK = 0x01 +local OPTIONS_OVERRIDE = 0x01 + local parent_ep = 10 local child1_ep = 20 local child2_ep = 30 local mock_device = test.mock_device.build_test_matter_device({ label = "Matter Switch", - profile = t_utils.get_profile_definition("plug-binary.yml"), + profile = t_utils.get_profile_definition("light-level-colorTemperature.yml"), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -48,36 +39,44 @@ local mock_device = test.mock_device.build_test_matter_device({ {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, }, device_types = { - {device_type_id = 0x010A, device_type_revision = 2} -- On/Off Plug + {device_type_id = 0x0100, device_type_revision = 2} -- On/Off Light } }, { endpoint_id = child1_ep, clusters = { {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2} }, device_types = { - {device_type_id = 0x010A, device_type_revision = 2} -- On/Off Plug + {device_type_id = 0x0100, device_type_revision = 2}, -- On/Off Light + {device_type_id = 0x0101, device_type_revision = 2} -- Dimmable Light } }, { endpoint_id = child2_ep, clusters = { {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2}, + {cluster_id = clusters.ColorControl.ID, cluster_type = "BOTH", feature_map = 30}, }, device_types = { - {device_type_id = 0x010A, device_type_revision = 2} -- On/Off Plug + {device_type_id = 0x010D, device_type_revision = 2} -- Extended Color Light } }, } }) -local mock_device_child_profile_override = test.mock_device.build_test_matter_device({ +local child1_ep_non_sequential = 50 +local child2_ep_non_sequential = 30 +local child3_ep_non_sequential = 40 + +local mock_device_parent_child_endpoints_non_sequential = test.mock_device.build_test_matter_device({ label = "Matter Switch", - profile = t_utils.get_profile_definition("switch-binary.yml"), + profile = t_utils.get_profile_definition("light-level-colorTemperature.yml"), manufacturer_info = { vendor_id = 0x1321, - product_id = 0x000D, + product_id = 0x000C, }, endpoints = { { @@ -95,20 +94,33 @@ local mock_device_child_profile_override = test.mock_device.build_test_matter_de {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, }, device_types = { - {device_type_id = 0x010A, device_type_revision = 2} -- On/Off Plug + {device_type_id = 0x0100, device_type_revision = 2} -- On/Off Light } }, { - endpoint_id = child1_ep, + endpoint_id = child1_ep_non_sequential, clusters = { {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2} }, device_types = { - {device_type_id = 0x010A, device_type_revision = 2} -- On/Off Plug + {device_type_id = 0x0100, device_type_revision = 2}, -- On/Off Light + {device_type_id = 0x0101, device_type_revision = 2} -- Dimmable Light } }, { - endpoint_id = child2_ep, + endpoint_id = child2_ep_non_sequential, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2}, + {cluster_id = clusters.ColorControl.ID, cluster_type = "BOTH", feature_map = 30}, + }, + device_types = { + {device_type_id = 0x010D, device_type_revision = 2} -- Extended Color Light + } + }, + { + endpoint_id = child3_ep_non_sequential, clusters = { {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, }, @@ -119,11 +131,16 @@ local mock_device_child_profile_override = test.mock_device.build_test_matter_de } }) +local child_profiles = { + [child1_ep] = t_utils.get_profile_definition("light-level.yml"), + [child2_ep] = t_utils.get_profile_definition("light-color-level.yml"), +} + local mock_children = {} for i, endpoint in ipairs(mock_device.endpoints) do if endpoint.endpoint_id ~= parent_ep and endpoint.endpoint_id ~= 0 then local child_data = { - profile = child_profile, + profile = child_profiles[endpoint.endpoint_id], device_network_id = string.format("%s:%d", mock_device.id, endpoint.endpoint_id), parent_device_id = mock_device.id, parent_assigned_child_key = string.format("%d", endpoint.endpoint_id) @@ -136,8 +153,24 @@ local function test_init() test.mock_device.add_test_device(mock_device) local cluster_subscribe_list = { clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel, + clusters.ColorControl.attributes.ColorTemperatureMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, + clusters.ColorControl.attributes.CurrentHue, + clusters.ColorControl.attributes.CurrentSaturation, + clusters.ColorControl.attributes.CurrentX, + clusters.ColorControl.attributes.CurrentY, + clusters.ColorControl.attributes.ColorMode, } 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.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) @@ -146,6 +179,10 @@ local function test_init() test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, child1_ep, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, child2_ep, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.ColorControl.attributes.Options:write(mock_device, child2_ep, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + mock_device:expect_metadata_update({ profile = "light-binary" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) for _, child in pairs(mock_children) do @@ -155,7 +192,7 @@ local function test_init() mock_device:expect_device_create({ type = "EDGE_CHILD", label = "Matter Switch 2", - profile = "plug-binary", + profile = "light-level", parent_device_id = mock_device.id, parent_assigned_child_key = string.format("%d", child1_ep) }) @@ -163,59 +200,94 @@ local function test_init() mock_device:expect_device_create({ type = "EDGE_CHILD", label = "Matter Switch 3", - profile = "plug-binary", + profile = "light-color-level", parent_device_id = mock_device.id, parent_assigned_child_key = string.format("%d", child2_ep) }) end -local mock_children_child_profile_override = {} -for i, endpoint in ipairs(mock_device_child_profile_override.endpoints) do +local child_profiles_non_sequential = { + [child1_ep_non_sequential] = t_utils.get_profile_definition("light-level.yml"), + [child2_ep_non_sequential] = t_utils.get_profile_definition("light-color-level.yml"), + [child3_ep_non_sequential] = t_utils.get_profile_definition("light-color-level.yml"), +} + +local mock_children_non_sequential = {} +for i, endpoint in ipairs(mock_device_parent_child_endpoints_non_sequential.endpoints) do if endpoint.endpoint_id ~= parent_ep and endpoint.endpoint_id ~= 0 then local child_data = { - profile = child_profile_override, - device_network_id = string.format("%s:%d", mock_device_child_profile_override.id, endpoint.endpoint_id), - parent_device_id = mock_device_child_profile_override.id, + profile = child_profiles_non_sequential[endpoint.endpoint_id], + device_network_id = string.format("%s:%d", mock_device_parent_child_endpoints_non_sequential.id, endpoint.endpoint_id), + parent_device_id = mock_device_parent_child_endpoints_non_sequential.id, parent_assigned_child_key = string.format("%d", endpoint.endpoint_id) } - mock_children_child_profile_override[endpoint.endpoint_id] = test.mock_device.build_test_child_device(child_data) + mock_children_non_sequential[endpoint.endpoint_id] = test.mock_device.build_test_child_device(child_data) end end -local function test_init_child_profile_override() - test.mock_device.add_test_device(mock_device_child_profile_override) +local function test_init_parent_child_endpoints_non_sequential() + test.mock_device.add_test_device(mock_device_parent_child_endpoints_non_sequential) local cluster_subscribe_list = { clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel, + clusters.ColorControl.attributes.ColorTemperatureMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, + clusters.ColorControl.attributes.CurrentHue, + clusters.ColorControl.attributes.CurrentSaturation, + clusters.ColorControl.attributes.CurrentX, + clusters.ColorControl.attributes.CurrentY, + clusters.ColorControl.attributes.ColorMode, } - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_child_profile_override) + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_parent_child_endpoints_non_sequential) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device_parent_child_endpoints_non_sequential)) + end + end - test.socket.device_lifecycle:__queue_receive({ mock_device_child_profile_override.id, "added" }) - test.socket.matter:__expect_send({mock_device_child_profile_override.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_endpoints_non_sequential.id, "added" }) + test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, subscribe_request}) - test.socket.device_lifecycle:__queue_receive({ mock_device_child_profile_override.id, "init" }) - test.socket.matter:__expect_send({mock_device_child_profile_override.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_endpoints_non_sequential.id, "init" }) + test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, subscribe_request}) - test.socket.device_lifecycle:__queue_receive({ mock_device_child_profile_override.id, "doConfigure" }) - mock_device_child_profile_override:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_endpoints_non_sequential.id, "doConfigure" }) + test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, clusters.LevelControl.attributes.Options:write(mock_device_parent_child_endpoints_non_sequential, child1_ep_non_sequential, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, clusters.LevelControl.attributes.Options:write(mock_device_parent_child_endpoints_non_sequential, child2_ep_non_sequential, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, clusters.ColorControl.attributes.Options:write(mock_device_parent_child_endpoints_non_sequential, child2_ep_non_sequential, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + mock_device_parent_child_endpoints_non_sequential:expect_metadata_update({ profile = "switch-binary" }) + mock_device_parent_child_endpoints_non_sequential:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - for _, child in pairs(mock_children_child_profile_override) do + for _, child in pairs(mock_children_non_sequential) do test.mock_device.add_test_device(child) end - mock_device:expect_device_create({ + mock_device_parent_child_endpoints_non_sequential:expect_device_create({ type = "EDGE_CHILD", label = "Matter Switch 2", - profile = "switch-binary", - parent_device_id = mock_device_child_profile_override.id, - parent_assigned_child_key = string.format("%d", child1_ep) + profile = "light-color-level", + parent_device_id = mock_device_parent_child_endpoints_non_sequential.id, + parent_assigned_child_key = string.format("%d", child2_ep_non_sequential) }) - mock_device:expect_device_create({ + -- switch-binary will be selected as an overridden child device profile + mock_device_parent_child_endpoints_non_sequential:expect_device_create({ type = "EDGE_CHILD", label = "Matter Switch 3", profile = "switch-binary", - parent_device_id = mock_device_child_profile_override.id, - parent_assigned_child_key = string.format("%d", child2_ep) + parent_device_id = mock_device_parent_child_endpoints_non_sequential.id, + parent_assigned_child_key = string.format("%d", child3_ep_non_sequential) + }) + + mock_device_parent_child_endpoints_non_sequential:expect_device_create({ + type = "EDGE_CHILD", + label = "Matter Switch 4", + profile = "light-level", + parent_device_id = mock_device_parent_child_endpoints_non_sequential.id, + parent_assigned_child_key = string.format("%d", child1_ep_non_sequential) }) end @@ -374,19 +446,244 @@ test.register_message_test( } ) -test.register_coroutine_test( - "Added should call refresh for child devices", function() - test.socket.matter:__set_channel_ordering("relaxed") - test.socket.device_lifecycle:__queue_receive({ mock_children[child1_ep].id, "added" }) - local req = clusters.OnOff.attributes.OnOff:read(mock_children[child1_ep]) - test.socket.matter:__expect_send({mock_device.id, req}) - end +test.register_message_test( + "Current level reports should generate appropriate events", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LevelControl.server.attributes.CurrentLevel:build_test_report_data(mock_device, child1_ep, 50) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[child1_ep]:generate_test_message("main", capabilities.switchLevel.level(math.floor((50 / 254.0 * 100) + 0.5))) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "switchLevel", capability_attr_id = "level" } + } + }, + } +) + +test.register_message_test( + "Set color temperature should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_children[child2_ep].id, + { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {1800} } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, child2_ep, 556, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.server.commands.MoveToColorTemperature:build_test_command_response(mock_device, child2_ep) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, child2_ep, 556) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[child2_ep]:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) + }, + } +) + +test.register_message_test( + "X and Y color values should report hue and saturation once both have been received", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, child2_ep, 15091) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, child2_ep, 21547) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[child2_ep]:generate_test_message("main", capabilities.colorControl.hue(50)) + }, + { + channel = "capability", + direction = "send", + message = mock_children[child2_ep]:generate_test_message("main", capabilities.colorControl.saturation(72)) + } + } +) + +test.register_message_test( + "Set color command should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_children[child2_ep].id, + { capability = "colorControl", component = "main", command = "setColor", args = { { hue = 50, saturation = 72 } } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.ColorControl.server.commands.MoveToColor(mock_device, child2_ep, 15182, 21547, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.server.commands.MoveToColor:build_test_command_response(mock_device, child2_ep) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, child2_ep, 15091) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, child2_ep, 21547) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[child2_ep]:generate_test_message("main", capabilities.colorControl.hue(50)) + }, + { + channel = "capability", + direction = "send", + message = mock_children[child2_ep]:generate_test_message("main", capabilities.colorControl.saturation(72)) + } + } +) + +test.register_message_test( + "Min and max level attributes set capability constraint for child devices", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LevelControl.attributes.MinLevel:build_test_report_data(mock_device, child1_ep, 1) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LevelControl.attributes.MaxLevel:build_test_report_data(mock_device, child1_ep, 254) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[child1_ep]:generate_test_message("main", capabilities.switchLevel.levelRange({minimum = 1, maximum = 100})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LevelControl.attributes.MinLevel:build_test_report_data(mock_device, child2_ep, 127) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LevelControl.attributes.MaxLevel:build_test_report_data(mock_device, child2_ep, 203) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[child2_ep]:generate_test_message("main", capabilities.switchLevel.levelRange({minimum = 50, maximum = 80})) + } + } +) + +test.register_message_test( + "Min and max color temp attributes set capability constraint for child devices", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds:build_test_report_data(mock_device, child2_ep, 153) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds:build_test_report_data(mock_device, child2_ep, 555) + } + }, + { + channel = "capability", + direction = "send", + message = mock_children[child2_ep]:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({minimum = 1800, maximum = 6500})) + } + } ) test.register_coroutine_test( - "Child device profiles should be overriden for specific devices", function() - end, - { test_init = test_init_child_profile_override } + "Test child devices are created in order of their endpoints", + function() + end, + { test_init = test_init_parent_child_endpoints_non_sequential } ) -test.run_registered_tests() +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/src/test/test_third_reality_mk1.lua b/drivers/SmartThings/matter-switch/src/test/test_third_reality_mk1.lua index 31f99f537d..0a72a1316e 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_third_reality_mk1.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_third_reality_mk1.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" diff --git a/drivers/SmartThings/matter-switch/src/utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/utils/device_configuration.lua deleted file mode 100644 index feb21ac193..0000000000 --- a/drivers/SmartThings/matter-switch/src/utils/device_configuration.lua +++ /dev/null @@ -1,273 +0,0 @@ --- Copyright 2025 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 embedded_cluster_utils = require "utils.embedded_cluster_utils" -local version = require "version" - -local fields = require "utils.switch_fields" -local switch_utils = require "utils.switch_utils" - --- Include driver-side definitions when lua libs api version is < 11 -if version.api < 11 then - clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement" - clusters.ElectricalPowerMeasurement = require "embedded_clusters.ElectricalPowerMeasurement" - clusters.ValveConfigurationAndControl = require "embedded_clusters.ValveConfigurationAndControl" -end - -local DeviceConfiguration = {} -local SwitchDeviceConfiguration = {} -local ButtonDeviceConfiguration = {} - -function SwitchDeviceConfiguration.assign_child_profile(device, child_ep) - local profile - - for _, ep in ipairs(device.endpoints) do - if ep.endpoint_id == child_ep then - -- Some devices report multiple device types which are a subset of - -- a superset device type (For example, Dimmable Light is a superset of - -- On/Off light). This mostly applies to the four light types, so we will want - -- to match the profile for the superset device type. This can be done by - -- matching to the device type with the highest ID - local id = 0 - for _, dt in ipairs(ep.device_types) do - id = math.max(id, dt.device_type_id) - end - profile = fields.device_type_profile_map[id] - break - end - end - - -- Check if device has an overridden child profile that differs from the profile that would match - -- the child's device type for the following two cases: - -- 1. To add Electrical Sensor only to the first EDGE_CHILD (light-power-energy-powerConsumption) - -- for the Aqara Light Switch H2. The profile of the second EDGE_CHILD for this device is - -- determined in the "for" loop above (e.g., light-binary) - -- 2. The selected profile for the child device matches the initial profile defined in - -- child_device_profile_overrides - for id, vendor in pairs(fields.child_device_profile_overrides_per_vendor_id) do - for _, fingerprint in ipairs(vendor) do - if device.manufacturer_info.product_id == fingerprint.product_id and - ((device.manufacturer_info.vendor_id == fields.AQARA_MANUFACTURER_ID and child_ep == 1) or profile == fingerprint.initial_profile) then - return fingerprint.target_profile - end - end - end - - -- default to "switch-binary" if no profile is found - return profile or "switch-binary" -end - -function SwitchDeviceConfiguration.create_child_switch_devices(driver, device, main_endpoint) - local num_switch_server_eps = 0 - local parent_child_device = false - local switch_eps = device:get_endpoints(clusters.OnOff.ID) - table.sort(switch_eps) - for idx, ep in ipairs(switch_eps) do - if device:supports_server_cluster(clusters.OnOff.ID, ep) then - num_switch_server_eps = num_switch_server_eps + 1 - if ep ~= main_endpoint then -- don't create a child device that maps to the main endpoint - local name = string.format("%s %d", device.label, num_switch_server_eps) - local child_profile = SwitchDeviceConfiguration.assign_child_profile(device, ep) - driver:try_create_device( - { - type = "EDGE_CHILD", - label = name, - profile = child_profile, - parent_device_id = device.id, - parent_assigned_child_key = string.format("%d", ep), - vendor_provided_label = name - } - ) - parent_child_device = true - if idx == 1 and string.find(child_profile, "energy") then - -- when energy management is defined in the root endpoint(0), replace it with the first switch endpoint and process it. - device:set_field(fields.ENERGY_MANAGEMENT_ENDPOINT, ep, {persist = true}) - end - end - end - end - - -- If the device is a parent child device, set the find_child function on init. This is persisted because initialize_buttons_and_switches - -- is only run once, but find_child function should be set on each driver init. - if parent_child_device then - device:set_field(fields.IS_PARENT_CHILD_DEVICE, true, {persist = true}) - end - - -- this is needed in initialize_buttons_and_switches - return num_switch_server_eps -end - -function SwitchDeviceConfiguration.update_devices_with_onOff_server_clusters(device, main_endpoint) - local cluster_id = 0 - for _, ep in ipairs(device.endpoints) do - -- main_endpoint only supports server cluster by definition of get_endpoints() - if main_endpoint == ep.endpoint_id then - for _, dt in ipairs(ep.device_types) do - -- no device type that is not in the switch subset should be considered. - if (fields.ON_OFF_SWITCH_ID <= dt.device_type_id and dt.device_type_id <= fields.ON_OFF_COLOR_DIMMER_SWITCH_ID) then - cluster_id = math.max(cluster_id, dt.device_type_id) - end - end - break - end - end - - if fields.device_type_profile_map[cluster_id] then - device:try_update_metadata({profile = fields.device_type_profile_map[cluster_id]}) - end -end - -function ButtonDeviceConfiguration.update_button_profile(device, main_endpoint, num_button_eps) - local profile_name = string.gsub(num_button_eps .. "-button", "1%-", "") -- remove the "1-" in a device with 1 button ep - if switch_utils.device_type_supports_button_switch_combination(device, main_endpoint) then - profile_name = "light-level-" .. profile_name - end - local battery_supported = #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) > 0 - if battery_supported then -- battery profiles are configured later, in power_source_attribute_list_handler - device:send(clusters.PowerSource.attributes.AttributeList:read(device)) - else - device:try_update_metadata({profile = profile_name}) - end -end - -function ButtonDeviceConfiguration.update_button_component_map(device, main_endpoint, button_eps) - -- create component mapping on the main profile button endpoints - table.sort(button_eps) - local component_map = {} - component_map["main"] = main_endpoint - for component_num, ep in ipairs(button_eps) do - if ep ~= main_endpoint then - local button_component = "button" - if #button_eps > 1 then - button_component = button_component .. component_num - end - component_map[button_component] = ep - end - end - device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, component_map, {persist = true}) -end - - -function ButtonDeviceConfiguration.configure_buttons(device) - local ms_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}) - local msr_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_RELEASE}) - local msl_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_LONG_PRESS}) - local msm_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS}) - - for _, ep in ipairs(ms_eps) do - if device.profile.components[switch_utils.endpoint_to_component(device, ep)] then - device.log.info_with({hub_logs=true}, string.format("Configuring Supported Values for generic switch endpoint %d", ep)) - local supportedButtonValues_event - -- this ordering is important, since MSM & MSL devices must also support MSR - if switch_utils.tbl_contains(msm_eps, ep) then - supportedButtonValues_event = nil -- deferred to the max press handler - device:send(clusters.Switch.attributes.MultiPressMax:read(device, ep)) - switch_utils.set_field_for_endpoint(device, fields.SUPPORTS_MULTI_PRESS, ep, true, {persist = true}) - elseif switch_utils.tbl_contains(msl_eps, ep) then - supportedButtonValues_event = capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}) - elseif switch_utils.tbl_contains(msr_eps, ep) then - supportedButtonValues_event = capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}) - switch_utils.set_field_for_endpoint(device, fields.EMULATE_HELD, ep, true, {persist = true}) - else -- this switch endpoint only supports momentary switch, no release events - supportedButtonValues_event = capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}) - switch_utils.set_field_for_endpoint(device, fields.INITIAL_PRESS_ONLY, ep, true, {persist = true}) - end - - if supportedButtonValues_event then - device:emit_event_for_endpoint(ep, supportedButtonValues_event) - end - device:emit_event_for_endpoint(ep, capabilities.button.button.pushed({state_change = false})) - else - device.log.info_with({hub_logs=true}, string.format("Component not found for generic switch endpoint %d. Skipping Supported Value configuration", ep)) - end - end -end - - --- [[ PROFILE MATCHING AND CONFIGURATIONS ]] -- - -function DeviceConfiguration.initialize_buttons_and_switches(driver, device, main_endpoint) - local profile_found = false - local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}) - if switch_utils.tbl_contains(fields.STATIC_BUTTON_PROFILE_SUPPORTED, #button_eps) then - ButtonDeviceConfiguration.update_button_profile(device, main_endpoint, #button_eps) - -- All button endpoints found will be added as additional components in the profile containing the main_endpoint. - -- The resulting endpoint to component map is saved in the COMPONENT_TO_ENDPOINT_MAP field - ButtonDeviceConfiguration.update_button_component_map(device, main_endpoint, button_eps) - ButtonDeviceConfiguration.configure_buttons(device) - profile_found = true - end - - -- Without support for bindings, only clusters that are implemented as server are counted. This count is handled - -- while building switch child profiles - local num_switch_server_eps = SwitchDeviceConfiguration.create_child_switch_devices(driver, device, main_endpoint) - - -- We do not support the Light Switch device types because they require OnOff to be implemented as 'client', which requires us to support bindings. - -- However, this workaround profiles devices that claim to be Light Switches, but that break spec and implement OnOff as 'server'. - -- Note: since their device type isn't supported, these devices join as a matter-thing. - if num_switch_server_eps > 0 and switch_utils.detect_matter_thing(device) then - SwitchDeviceConfiguration.update_devices_with_onOff_server_clusters(device, main_endpoint) - profile_found = true - end - return profile_found -end - -function DeviceConfiguration.match_profile(driver, device) - local main_endpoint = switch_utils.find_default_endpoint(device) - -- initialize the main device card with buttons if applicable, and create child devices as needed for multi-switch devices. - local profile_found = DeviceConfiguration.initialize_buttons_and_switches(driver, device, main_endpoint) - if device:get_field(fields.IS_PARENT_CHILD_DEVICE) then - device:set_find_child(switch_utils.find_child) - end - if profile_found then - return - end - - local fan_eps = device:get_endpoints(clusters.FanControl.ID) - local level_eps = device:get_endpoints(clusters.LevelControl.ID) - local energy_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID) - local power_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalPowerMeasurement.ID) - local valve_eps = embedded_cluster_utils.get_endpoints(device, clusters.ValveConfigurationAndControl.ID) - local profile_name = nil - local level_support = "" - if #level_eps > 0 then - level_support = "-level" - end - if #energy_eps > 0 and #power_eps > 0 then - profile_name = "plug" .. level_support .. "-power-energy-powerConsumption" - elseif #energy_eps > 0 then - profile_name = "plug" .. level_support .. "-energy-powerConsumption" - elseif #power_eps > 0 then - profile_name = "plug" .. level_support .. "-power" - elseif #valve_eps > 0 then - profile_name = "water-valve" - if #embedded_cluster_utils.get_endpoints(device, clusters.ValveConfigurationAndControl.ID, - {feature_bitmap = clusters.ValveConfigurationAndControl.types.Feature.LEVEL}) > 0 then - profile_name = profile_name .. "-level" - end - elseif #fan_eps > 0 then - profile_name = "light-color-level-fan" - end - if profile_name then - device:try_update_metadata({ profile = profile_name }) - end -end - -return { - DeviceCfg = DeviceConfiguration, - SwitchCfg = SwitchDeviceConfiguration, - ButtonCfg = ButtonDeviceConfiguration -} diff --git a/drivers/SmartThings/matter-switch/src/utils/switch_fields.lua b/drivers/SmartThings/matter-switch/src/utils/switch_fields.lua deleted file mode 100644 index 2244eab661..0000000000 --- a/drivers/SmartThings/matter-switch/src/utils/switch_fields.lua +++ /dev/null @@ -1,261 +0,0 @@ --- Copyright 2025 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 clusters = require "st.matter.clusters" -local capabilities = require "st.capabilities" -local version = require "version" - --- Include driver-side definitions when lua libs api version is < 11 -if version.api < 11 then - clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement" - clusters.ElectricalPowerMeasurement = require "embedded_clusters.ElectricalPowerMeasurement" -end - -local SwitchFields = {} - -SwitchFields.HUE_SAT_COLOR_MODE = clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION -SwitchFields.X_Y_COLOR_MODE = clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY - -SwitchFields.MOST_RECENT_TEMP = "mostRecentTemp" -SwitchFields.RECEIVED_X = "receivedX" -SwitchFields.RECEIVED_Y = "receivedY" -SwitchFields.HUESAT_SUPPORT = "huesatSupport" - - -SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT = 1000000 - --- These values are a "sanity check" to check that values we are getting are reasonable -local COLOR_TEMPERATURE_KELVIN_MAX = 15000 -local COLOR_TEMPERATURE_KELVIN_MIN = 1000 -SwitchFields.COLOR_TEMPERATURE_MIRED_MAX = SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MIN -SwitchFields.COLOR_TEMPERATURE_MIRED_MIN = SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MAX - -SwitchFields.SWITCH_LEVEL_LIGHTING_MIN = 1 -SwitchFields.CURRENT_HUESAT_ATTR_MIN = 0 -SwitchFields.CURRENT_HUESAT_ATTR_MAX = 254 - - --- DEVICE TYPES -SwitchFields.AGGREGATOR_DEVICE_TYPE_ID = 0x000E -SwitchFields.ON_OFF_LIGHT_DEVICE_TYPE_ID = 0x0100 -SwitchFields.DIMMABLE_LIGHT_DEVICE_TYPE_ID = 0x0101 -SwitchFields.COLOR_TEMP_LIGHT_DEVICE_TYPE_ID = 0x010C -SwitchFields.EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID = 0x010D -SwitchFields.ON_OFF_PLUG_DEVICE_TYPE_ID = 0x010A -SwitchFields.DIMMABLE_PLUG_DEVICE_TYPE_ID = 0x010B -SwitchFields.ON_OFF_SWITCH_ID = 0x0103 -SwitchFields.ON_OFF_DIMMER_SWITCH_ID = 0x0104 -SwitchFields.ON_OFF_COLOR_DIMMER_SWITCH_ID = 0x0105 -SwitchFields.MOUNTED_ON_OFF_CONTROL_ID = 0x010F -SwitchFields.MOUNTED_DIMMABLE_LOAD_CONTROL_ID = 0x0110 -SwitchFields.GENERIC_SWITCH_ID = 0x000F -SwitchFields.ELECTRICAL_SENSOR_ID = 0x0510 - -SwitchFields.device_type_profile_map = { - [SwitchFields.ON_OFF_LIGHT_DEVICE_TYPE_ID] = "light-binary", - [SwitchFields.DIMMABLE_LIGHT_DEVICE_TYPE_ID] = "light-level", - [SwitchFields.COLOR_TEMP_LIGHT_DEVICE_TYPE_ID] = "light-level-colorTemperature", - [SwitchFields.EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID] = "light-color-level", - [SwitchFields.ON_OFF_PLUG_DEVICE_TYPE_ID] = "plug-binary", - [SwitchFields.DIMMABLE_PLUG_DEVICE_TYPE_ID] = "plug-level", - [SwitchFields.ON_OFF_SWITCH_ID] = "switch-binary", - [SwitchFields.ON_OFF_DIMMER_SWITCH_ID] = "switch-level", - [SwitchFields.ON_OFF_COLOR_DIMMER_SWITCH_ID] = "switch-color-level", - [SwitchFields.MOUNTED_ON_OFF_CONTROL_ID] = "switch-binary", - [SwitchFields.MOUNTED_DIMMABLE_LOAD_CONTROL_ID] = "switch-level", -} - - -SwitchFields.CONVERSION_CONST_MILLIWATT_TO_WATT = 1000 -- A milliwatt is 1/1000th of a watt - - --- COMPONENT_TO_ENDPOINT_MAP is here to preserve the endpoint mapping for --- devices that were joined to this driver as MCD devices before the transition --- to join switch devices as parent-child. This value will exist in the device --- table for devices that joined prior to this transition, and is also used for --- button devices that require component mapping. -SwitchFields.COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map" -SwitchFields.ENERGY_MANAGEMENT_ENDPOINT = "__energy_management_endpoint" -SwitchFields.IS_PARENT_CHILD_DEVICE = "__is_parent_child_device" -SwitchFields.COLOR_TEMP_BOUND_RECEIVED_KELVIN = "__colorTemp_bound_received_kelvin" -SwitchFields.COLOR_TEMP_BOUND_RECEIVED_MIRED = "__colorTemp_bound_received_mired" -SwitchFields.COLOR_TEMP_MIN = "__color_temp_min" -SwitchFields.COLOR_TEMP_MAX = "__color_temp_max" -SwitchFields.LEVEL_BOUND_RECEIVED = "__level_bound_received" -SwitchFields.LEVEL_MIN = "__level_min" -SwitchFields.LEVEL_MAX = "__level_max" -SwitchFields.COLOR_MODE = "__color_mode" - -SwitchFields.updated_fields = { - { current_field_name = "__component_to_endpoint_map_button", updated_field_name = SwitchFields.COMPONENT_TO_ENDPOINT_MAP }, - { current_field_name = "__switch_intialized", updated_field_name = nil } -} - -SwitchFields.HUE_SAT_COLOR_MODE = clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION -SwitchFields.X_Y_COLOR_MODE = clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY - - -SwitchFields.child_device_profile_overrides_per_vendor_id = { - [0x1321] = { - { product_id = 0x000C, target_profile = "switch-binary", initial_profile = "plug-binary" }, - { product_id = 0x000D, target_profile = "switch-binary", initial_profile = "plug-binary" }, - }, - [0x115F] = { - { product_id = 0x1003, target_profile = "light-power-energy-powerConsumption" }, -- 2 Buttons(Generic Switch), 1 Channel(On/Off Light) - { product_id = 0x1004, target_profile = "light-power-energy-powerConsumption" }, -- 2 Buttons(Generic Switch), 2 Channels(On/Off Light) - { product_id = 0x1005, target_profile = "light-power-energy-powerConsumption" }, -- 4 Buttons(Generic Switch), 3 Channels(On/Off Light) - { product_id = 0x1006, target_profile = "light-level-power-energy-powerConsumption" }, -- 3 Buttons(Generic Switch), 1 Channels(Dimmable Light) - { product_id = 0x1008, target_profile = "light-power-energy-powerConsumption" }, -- 2 Buttons(Generic Switch), 1 Channel(On/Off Light) - { product_id = 0x1009, target_profile = "light-power-energy-powerConsumption" }, -- 4 Buttons(Generic Switch), 2 Channels(On/Off Light) - { product_id = 0x100A, target_profile = "light-level-power-energy-powerConsumption" }, -- 1 Buttons(Generic Switch), 1 Channels(Dimmable Light) - } -} - -SwitchFields.CUMULATIVE_REPORTS_NOT_SUPPORTED = "__cumulative_reports_not_supported" -SwitchFields.TOTAL_IMPORTED_ENERGY = "__total_imported_energy" -SwitchFields.LAST_IMPORTED_REPORT_TIMESTAMP = "__last_imported_report_timestamp" -SwitchFields.MINIMUM_ST_ENERGY_REPORT_INTERVAL = (15 * 60) -- 15 minutes, reported in seconds - -SwitchFields.START_BUTTON_PRESS = "__start_button_press" -SwitchFields.TIMEOUT_THRESHOLD = 10 --arbitrary timeout -SwitchFields.HELD_THRESHOLD = 1 - --- this is the number of buttons for which we have a static profile already made -SwitchFields.STATIC_BUTTON_PROFILE_SUPPORTED = {1, 2, 3, 4, 5, 6, 7, 8, 9} - --- Some switches will send a MultiPressComplete event as part of a long press sequence. Normally the driver will create a --- button capability event on receipt of MultiPressComplete, but in this case that would result in an extra event because --- the "held" capability event is generated when the LongPress event is received. The IGNORE_NEXT_MPC flag is used --- to tell the driver to ignore MultiPressComplete if it is received after a long press to avoid this extra event. -SwitchFields.IGNORE_NEXT_MPC = "__ignore_next_mpc" - --- These are essentially storing the supported features of a given endpoint --- TODO: add an is_feature_supported_for_endpoint function to matter.device that takes an endpoint -SwitchFields.EMULATE_HELD = "__emulate_held" -- for non-MSR (MomentarySwitchRelease) devices we can emulate this on the software side -SwitchFields.SUPPORTS_MULTI_PRESS = "__multi_button" -- for MSM devices (MomentarySwitchMultiPress), create an event on receipt of MultiPressComplete -SwitchFields.INITIAL_PRESS_ONLY = "__initial_press_only" -- for devices that support MS (MomentarySwitch), but not MSR (MomentarySwitchRelease) - -SwitchFields.TEMP_BOUND_RECEIVED = "__temp_bound_received" -SwitchFields.TEMP_MIN = "__temp_min" -SwitchFields.TEMP_MAX = "__temp_max" - -SwitchFields.AQARA_MANUFACTURER_ID = 0x115F -SwitchFields.AQARA_CLIMATE_SENSOR_W100_ID = 0x2004 - -SwitchFields.TRANSITION_TIME = 0 --1/10ths of a second --- When sent with a command, these options mask and override bitmaps cause the command --- to take effect when the switch/light is off. -SwitchFields.OPTIONS_MASK = 0x01 -SwitchFields.OPTIONS_OVERRIDE = 0x01 - - -SwitchFields.supported_capabilities = { - capabilities.battery, - capabilities.batteryLevel, - capabilities.button, - capabilities.colorControl, - capabilities.colorTemperature, - capabilities.energyMeter, - capabilities.fanMode, - capabilities.fanSpeedPercent, - capabilities.illuminanceMeasurement, - capabilities.level, - capabilities.motionSensor, - capabilities.powerMeter, - capabilities.powerConsumptionReport, - capabilities.relativeHumidityMeasurement, - capabilities.switch, - capabilities.switchLevel, - capabilities.temperatureMeasurement, - capabilities.valve, -} - -SwitchFields.device_type_attribute_map = { - [SwitchFields.ON_OFF_LIGHT_DEVICE_TYPE_ID] = { - clusters.OnOff.attributes.OnOff - }, - [SwitchFields.DIMMABLE_LIGHT_DEVICE_TYPE_ID] = { - clusters.OnOff.attributes.OnOff, - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel - }, - [SwitchFields.COLOR_TEMP_LIGHT_DEVICE_TYPE_ID] = { - clusters.OnOff.attributes.OnOff, - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel, - clusters.ColorControl.attributes.ColorTemperatureMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds - }, - [SwitchFields.EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID] = { - clusters.OnOff.attributes.OnOff, - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel, - clusters.ColorControl.attributes.ColorTemperatureMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, - clusters.ColorControl.attributes.CurrentHue, - clusters.ColorControl.attributes.CurrentSaturation, - clusters.ColorControl.attributes.CurrentX, - clusters.ColorControl.attributes.CurrentY - }, - [SwitchFields.ON_OFF_PLUG_DEVICE_TYPE_ID] = { - clusters.OnOff.attributes.OnOff - }, - [SwitchFields.DIMMABLE_PLUG_DEVICE_TYPE_ID] = { - clusters.OnOff.attributes.OnOff, - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel - }, - [SwitchFields.ON_OFF_SWITCH_ID] = { - clusters.OnOff.attributes.OnOff - }, - [SwitchFields.ON_OFF_DIMMER_SWITCH_ID] = { - clusters.OnOff.attributes.OnOff, - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel - }, - [SwitchFields.ON_OFF_COLOR_DIMMER_SWITCH_ID] = { - clusters.OnOff.attributes.OnOff, - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel, - clusters.ColorControl.attributes.ColorTemperatureMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, - clusters.ColorControl.attributes.CurrentHue, - clusters.ColorControl.attributes.CurrentSaturation, - clusters.ColorControl.attributes.CurrentX, - clusters.ColorControl.attributes.CurrentY - }, - [SwitchFields.GENERIC_SWITCH_ID] = { - clusters.PowerSource.attributes.BatPercentRemaining, - clusters.Switch.events.InitialPress, - clusters.Switch.events.LongPress, - clusters.Switch.events.ShortRelease, - clusters.Switch.events.MultiPressComplete - }, - [SwitchFields.ELECTRICAL_SENSOR_ID] = { - clusters.ElectricalPowerMeasurement.attributes.ActivePower, - clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, - clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported - } -} - -return SwitchFields \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/src/utils/switch_utils.lua b/drivers/SmartThings/matter-switch/src/utils/switch_utils.lua deleted file mode 100644 index 86368e9208..0000000000 --- a/drivers/SmartThings/matter-switch/src/utils/switch_utils.lua +++ /dev/null @@ -1,244 +0,0 @@ --- Copyright 2025 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 fields = require "utils.switch_fields" -local st_utils = require "st.utils" -local clusters = require "st.matter.clusters" -local capabilities = require "st.capabilities" -local log = require "log" - -local utils = {} - -function utils.tbl_contains(array, value) - for _, element in ipairs(array) do - if element == value then - return true - end - end - return false -end - -function utils.convert_huesat_st_to_matter(val) - return st_utils.clamp_value(math.floor((val * 0xFE) / 100.0 + 0.5), fields.CURRENT_HUESAT_ATTR_MIN, fields.CURRENT_HUESAT_ATTR_MAX) -end - -function utils.get_field_for_endpoint(device, field, endpoint) - return device:get_field(string.format("%s_%d", field, endpoint)) -end - -function utils.set_field_for_endpoint(device, field, endpoint, value, additional_params) - device:set_field(string.format("%s_%d", field, endpoint), value, additional_params) -end - -function utils.mired_to_kelvin(value, minOrMax) - if value == 0 then -- shouldn't happen, but has - value = 1 - log.warn(string.format("Received a color temperature of 0 mireds. Using a color temperature of 1 mired to avoid divide by zero")) - end - -- We divide inside the rounding and multiply outside of it because we expect these - -- bounds to be multiples of 100. For the maximum mired value (minimum K value), - -- add 1 before converting and round up to nearest hundreds. For the minimum mired - -- (maximum K value) value, subtract 1 before converting and round down to nearest - -- hundreds. Note that 1 is added/subtracted from the mired value in order to avoid - -- rounding errors from the conversion of Kelvin to mireds. - local kelvin_step_size = 100 - local rounding_value = 0.5 - if minOrMax == fields.COLOR_TEMP_MIN then - return st_utils.round(fields.MIRED_KELVIN_CONVERSION_CONSTANT / (kelvin_step_size * (value + 1)) + rounding_value) * kelvin_step_size - elseif minOrMax == fields.COLOR_TEMP_MAX then - return st_utils.round(fields.MIRED_KELVIN_CONVERSION_CONSTANT / (kelvin_step_size * (value - 1)) - rounding_value) * kelvin_step_size - else - log.warn_with({hub_logs = true}, "Attempted to convert temperature unit for an undefined value") - end -end - -function utils.check_field_name_updates(device) - for _, field in ipairs(fields.updated_fields) do - if device:get_field(field.current_field_name) then - if field.updated_field_name ~= nil then - device:set_field(field.updated_field_name, device:get_field(field.current_field_name), {persist = true}) - end - device:set_field(field.current_field_name, nil) - end - end -end - ---- device_type_supports_button_switch_combination helper function used to check ---- whether the device type for an endpoint is currently supported by a profile for ---- combination button/switch devices. -function utils.device_type_supports_button_switch_combination(device, endpoint_id) - for _, ep in ipairs(device.endpoints) do - if ep.endpoint_id == endpoint_id then - for _, dt in ipairs(ep.device_types) do - if dt.device_type_id == fields.DIMMABLE_LIGHT_DEVICE_TYPE_ID then - for _, fingerprint in ipairs(fields.child_device_profile_overrides_per_vendor_id[0x115F]) do - if device.manufacturer_info.product_id == fingerprint.product_id then - return false -- For Aqara Dimmer Switch with Button. - end - end - return true - end - end - end - end - return false -end - ---- find_default_endpoint is a helper function to handle situations where ---- device does not have endpoint ids in sequential order from 1 -function utils.find_default_endpoint(device) - if device.manufacturer_info.vendor_id == fields.AQARA_MANUFACTURER_ID and - device.manufacturer_info.product_id == fields.AQARA_CLIMATE_SENSOR_W100_ID then - -- In case of Aqara Climate Sensor W100, in order to sequentially set the button name to button 1, 2, 3 - return device.MATTER_DEFAULT_ENDPOINT - end - - local switch_eps = device:get_endpoints(clusters.OnOff.ID) - local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}) - - local get_first_non_zero_endpoint = function(endpoints) - table.sort(endpoints) - for _,ep in ipairs(endpoints) do - if ep ~= 0 then -- 0 is the matter RootNode endpoint - return ep - end - end - return nil - end - - -- Return the first switch endpoint as the default endpoint if no button endpoints are present - if #button_eps == 0 and #switch_eps > 0 then - return get_first_non_zero_endpoint(switch_eps) - end - - -- Return the first button endpoint as the default endpoint if no switch endpoints are present - if #switch_eps == 0 and #button_eps > 0 then - return get_first_non_zero_endpoint(button_eps) - end - - -- If both switch and button endpoints are present, check the device type on the main switch - -- endpoint. If it is not a supported device type, return the first button endpoint as the - -- default endpoint. - if #switch_eps > 0 and #button_eps > 0 then - local main_endpoint = get_first_non_zero_endpoint(switch_eps) - if utils.device_type_supports_button_switch_combination(device, main_endpoint) then - return main_endpoint - else - device.log.warn("The main switch endpoint does not contain a supported device type for a component configuration with buttons") - return get_first_non_zero_endpoint(button_eps) - end - end - - device.log.warn(string.format("Did not find default endpoint, will use endpoint %d instead", device.MATTER_DEFAULT_ENDPOINT)) - return device.MATTER_DEFAULT_ENDPOINT -end - -function utils.component_to_endpoint(device, component) - local map = device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) or {} - if map[component] then - return map[component] - end - return utils.find_default_endpoint(device) -end - -function utils.endpoint_to_component(device, ep) - local map = device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) or {} - for component, endpoint in pairs(map) do - if endpoint == ep then - return component - end - end - return "main" -end - -function utils.find_child(parent, ep_id) - return parent:get_child_by_parent_assigned_key(string.format("%d", ep_id)) -end - --- Fallback handler for responses that dont have their own handler -function utils.matter_handler(driver, device, response_block) - device.log.info(string.format("Fallback handler for %s", response_block)) -end - ---helper function to create list of multi press values -function utils.create_multi_press_values_list(size, supportsHeld) - local list = {"pushed", "double"} - if supportsHeld then table.insert(list, "held") end - -- add multi press values of 3 or greater to the list - for i=3, size do - table.insert(list, string.format("pushed_%dx", i)) - end - return list -end - -function utils.detect_bridge(device) - for _, ep in ipairs(device.endpoints) do - for _, dt in ipairs(ep.device_types) do - if dt.device_type_id == fields.AGGREGATOR_DEVICE_TYPE_ID then - return true - end - end - end - return false -end - -function utils.detect_matter_thing(device) - for _, capability in ipairs(fields.supported_capabilities) do - if device:supports_capability(capability) then - return false - end - end - return device:supports_capability(capabilities.refresh) -end - -function utils.report_power_consumption_to_st_energy(device, latest_total_imported_energy_wh) - local current_time = os.time() - local last_time = device:get_field(fields.LAST_IMPORTED_REPORT_TIMESTAMP) or 0 - - -- Ensure that the previous report was sent at least 15 minutes ago - if fields.MINIMUM_ST_ENERGY_REPORT_INTERVAL >= (current_time - last_time) then - return - end - - device:set_field(fields.LAST_IMPORTED_REPORT_TIMESTAMP, current_time, { persist = true }) - - -- Calculate the energy delta between reports - local energy_delta_wh = 0.0 - local previous_imported_report = device:get_latest_state("main", capabilities.powerConsumptionReport.ID, - capabilities.powerConsumptionReport.powerConsumption.NAME) - if previous_imported_report and previous_imported_report.energy then - energy_delta_wh = math.max(latest_total_imported_energy_wh - previous_imported_report.energy, 0.0) - end - - local epoch_to_iso8601 = function(time) return os.date("!%Y-%m-%dT%H:%M:%SZ", time) end -- Return an ISO-8061 timestamp from UTC - - -- Report the energy consumed during the time interval. The unit of these values should be 'Wh' - if not device:get_field(fields.ENERGY_MANAGEMENT_ENDPOINT) then - device:emit_event(capabilities.powerConsumptionReport.powerConsumption({ - start = epoch_to_iso8601(last_time), - ["end"] = epoch_to_iso8601(current_time - 1), - deltaEnergy = energy_delta_wh, - energy = latest_total_imported_energy_wh - })) - else - device:emit_event_for_endpoint(device:get_field(fields.ENERGY_MANAGEMENT_ENDPOINT),capabilities.powerConsumptionReport.powerConsumption({ - start = epoch_to_iso8601(last_time), - ["end"] = epoch_to_iso8601(current_time - 1), - deltaEnergy = energy_delta_wh, - energy = latest_total_imported_energy_wh - })) - end -end - -return utils diff --git a/drivers/SmartThings/matter-thermostat/fingerprints.yml b/drivers/SmartThings/matter-thermostat/fingerprints.yml index 8358e3164e..30e99caadb 100644 --- a/drivers/SmartThings/matter-thermostat/fingerprints.yml +++ b/drivers/SmartThings/matter-thermostat/fingerprints.yml @@ -16,6 +16,12 @@ matterManufacturer: vendorId: 0x1209 productId: 0x3012 deviceProfileName: thermostat-heating-only-nostate + #Cync + - id: "4921/121" + deviceLabel: Cync Fan Switch + vendorId: 0x1339 + productId: 0x0079 + deviceProfileName: fan-generic #Eve - id: "4874/79" deviceLabel: Eve Thermo @@ -27,6 +33,11 @@ matterManufacturer: vendorId: 0x130A productId: 0x007D deviceProfileName: thermostat-heating-only-nostate + - id: "4874/127" + deviceLabel: Eve Thermostat (Europe) + vendorId: 0x130A + productId: 0x007F + deviceProfileName: thermostat-heating-only-nostate-nobattery #Habi - id: "5415/1" deviceLabel: habi Matter Wireless Room Thermostat @@ -44,6 +55,11 @@ matterManufacturer: vendorId: 0x1206 productId: 0x0001 deviceProfileName: thermostat-nostate-nobattery + - id: "4614/17" + deviceLabel: LUX TQX Smart Thermostat + vendorId: 0x1206 + productId: 0x0011 + deviceProfileName: thermostat-humidity-nostate-nobattery #Meross - id: "4933/57345" deviceLabel: Smart Wi-Fi Thermostat diff --git a/drivers/SmartThings/matter-thermostat/profiles/thermostat-modular.yml b/drivers/SmartThings/matter-thermostat/profiles/thermostat-modular.yml index 1e7bd97dc7..7d3dd309f2 100644 --- a/drivers/SmartThings/matter-thermostat/profiles/thermostat-modular.yml +++ b/drivers/SmartThings/matter-thermostat/profiles/thermostat-modular.yml @@ -9,6 +9,9 @@ components: - id: fanMode version: 1 optional: true + - id: fanSpeedPercent + version: 1 + optional: true - id: fanOscillationMode version: 1 optional: true diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua deleted file mode 100644 index 320826dacc..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua +++ /dev/null @@ -1,76 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" - -local AttributeList = { - ID = 0xFFFB, - NAME = "AttributeList", - base_type = require "st.matter.data_types.Array", - element_type = require "st.matter.data_types.Uint32", -} - -function AttributeList:augment_type(data_type_obj) - for i, v in ipairs(data_type_obj.elements) do - data_type_obj.elements[i] = data_types.validate_or_build_type(v, AttributeList.element_type) - end -end - -function AttributeList:new_value(...) - local o = self.base_type(table.unpack({...})) - - return o -end - -function AttributeList:read(device, endpoint_id) - return cluster_base.read( - device, - endpoint_id, - self._cluster.ID, - self.ID, - nil --event_id - ) -end - - -function AttributeList:subscribe(device, endpoint_id) - return cluster_base.subscribe( - device, - endpoint_id, - self._cluster.ID, - self.ID, - nil --event_id - ) -end - -function AttributeList:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function AttributeList:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function AttributeList:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(AttributeList, {__call = AttributeList.new_value, __index = AttributeList.base_type}) -return AttributeList - diff --git a/drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/AcceptedCommandList.lua deleted file mode 100644 index 6a8d95df1d..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/AcceptedCommandList.lua +++ /dev/null @@ -1,75 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" - -local AcceptedCommandList = { - ID = 0xFFF9, - NAME = "AcceptedCommandList", - base_type = require "st.matter.data_types.Array", - element_type = require "st.matter.data_types.Uint32", -} - -function AcceptedCommandList:augment_type(data_type_obj) - for i, v in ipairs(data_type_obj.elements) do - data_type_obj.elements[i] = data_types.validate_or_build_type(v, AcceptedCommandList.element_type) - end -end - -function AcceptedCommandList:new_value(...) - local o = self.base_type(table.unpack({...})) - - return o -end - -function AcceptedCommandList:read(device, endpoint_id) - return cluster_base.read( - device, - endpoint_id, - self._cluster.ID, - self.ID, - nil - ) -end - -function AcceptedCommandList:subscribe(device, endpoint_id) - return cluster_base.subscribe( - device, - endpoint_id, - self._cluster.ID, - self.ID, - nil - ) -end - -function AcceptedCommandList:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function AcceptedCommandList:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function AcceptedCommandList:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value, __index = AcceptedCommandList.base_type}) -return AcceptedCommandList - diff --git a/drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/CarbonDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyExported.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyExported.lua deleted file mode 100644 index 22befec642..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyExported.lua +++ /dev/null @@ -1,68 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" - -local CumulativeEnergyExported = { - ID = 0x0002, - NAME = "CumulativeEnergyExported", - base_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", -} - -function CumulativeEnergyExported:new_value(...) - local o = self.base_type(table.unpack({...})) - self:augment_type(o) - return o -end - -function CumulativeEnergyExported:read(device, endpoint_id) - return cluster_base.read( - device, - endpoint_id, - self._cluster.ID, - self.ID, - nil - ) -end - -function CumulativeEnergyExported:subscribe(device, endpoint_id) - return cluster_base.subscribe( - device, - endpoint_id, - self._cluster.ID, - self.ID, - nil - ) -end - -function CumulativeEnergyExported:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function CumulativeEnergyExported:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - self:augment_type(data) - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function CumulativeEnergyExported:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(CumulativeEnergyExported, {__call = CumulativeEnergyExported.new_value, __index = CumulativeEnergyExported.base_type}) -return CumulativeEnergyExported - diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyExported.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyExported.lua deleted file mode 100644 index 4c1ee29274..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyExported.lua +++ /dev/null @@ -1,68 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" - -local PeriodicEnergyExported = { - ID = 0x0004, - NAME = "PeriodicEnergyExported", - base_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", -} - -function PeriodicEnergyExported:new_value(...) - local o = self.base_type(table.unpack({...})) - self:augment_type(o) - return o -end - -function PeriodicEnergyExported:read(device, endpoint_id) - return cluster_base.read( - device, - endpoint_id, - self._cluster.ID, - self.ID, - nil - ) -end - -function PeriodicEnergyExported:subscribe(device, endpoint_id) - return cluster_base.subscribe( - device, - endpoint_id, - self._cluster.ID, - self.ID, - nil - ) -end - -function PeriodicEnergyExported:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function PeriodicEnergyExported:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - self:augment_type(data) - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function PeriodicEnergyExported:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(PeriodicEnergyExported, {__call = PeriodicEnergyExported.new_value, __index = PeriodicEnergyExported.base_type}) -return PeriodicEnergyExported - diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/CumulativeEnergyMeasured.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/CumulativeEnergyMeasured.lua deleted file mode 100644 index 13136bd791..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/CumulativeEnergyMeasured.lua +++ /dev/null @@ -1,108 +0,0 @@ -local data_types = require "st.matter.data_types" -local cluster_base = require "st.matter.cluster_base" -local TLVParser = require "st.matter.TLV.TLVParser" -local StructureABC = require "st.matter.data_types.base_defs.StructureABC" - -local CumulativeEnergyMeasured = { - ID = 0x0000, - NAME = "CumulativeEnergyMeasured", - base_type = data_types.Structure, -} - -CumulativeEnergyMeasured.field_defs = { - { - name = "energy_imported", - field_id = 0, - is_nullable = false, - is_optional = true, - data_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", - }, - { - name = "energy_exported", - field_id = 1, - is_nullable = false, - is_optional = true, - data_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", - }, -} - -function CumulativeEnergyMeasured:augment_type(base_type_obj) - local elems = {} - for _, v in ipairs(base_type_obj.elements) do - for _, field_def in ipairs(self.field_defs) do - if field_def.field_id == v.field_id and not - ((field_def.is_nullable or field_def.is_optional) and v.elements == nil) then - elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) - if field_def.element_type ~= nil then - for i, e in ipairs(elems[field_def.name].elements) do - elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) - end - end - end - end - end - base_type_obj.elements = elems -end - -function CumulativeEnergyMeasured:read(device, endpoint_id) - return cluster_base.read( - device, - endpoint_id, - self._cluster.ID, - nil, --attribute_id - self.ID - ) -end - -function CumulativeEnergyMeasured:subscribe(device, endpoint_id) - return cluster_base.subscribe( - device, - endpoint_id, - self._cluster.ID, - nil, --attribute_id - self.ID - ) -end - -function CumulativeEnergyMeasured:build_test_event_report( - device, - endpoint_id, - fields, - status -) - local data = {} - data.elements = {} - data.num_elements = 0 - setmetatable(data, StructureABC.new_mt({NAME = "CumulativeEnergyMeasuredEventData", ID = 0x15})) - for idx, field_def in ipairs(self.field_defs) do --Note: idx is 1 when field_id is 0 - if (not field_def.is_optional and not field_def.is_nullable) and not fields[field_def.name] then - error("Missing non optional or non_nullable field: " .. field_def.name) - elseif fields[field_def.name] then - data.elements[field_def.name] = data_types.validate_or_build_type(fields[field_def.name], field_def.data_type, field_def.name) - data.elements[field_def.name].field_id = field_def.field_id - data.num_elements = data.num_elements + 1 - end - end - return cluster_base.build_test_event_report( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function CumulativeEnergyMeasured:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -function CumulativeEnergyMeasured:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -return CumulativeEnergyMeasured - diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/PeriodicEnergyMeasured.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/PeriodicEnergyMeasured.lua deleted file mode 100644 index e2c4b1b577..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/PeriodicEnergyMeasured.lua +++ /dev/null @@ -1,108 +0,0 @@ -local data_types = require "st.matter.data_types" -local cluster_base = require "st.matter.cluster_base" -local TLVParser = require "st.matter.TLV.TLVParser" -local StructureABC = require "st.matter.data_types.base_defs.StructureABC" - -local PeriodicEnergyMeasured = { - ID = 0x0001, - NAME = "PeriodicEnergyMeasured", - base_type = data_types.Structure, -} - -PeriodicEnergyMeasured.field_defs = { - { - name = "energy_imported", - field_id = 0, - is_nullable = false, - is_optional = true, - data_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", - }, - { - name = "energy_exported", - field_id = 1, - is_nullable = false, - is_optional = true, - data_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", - }, -} - -function PeriodicEnergyMeasured:augment_type(base_type_obj) - local elems = {} - for _, v in ipairs(base_type_obj.elements) do - for _, field_def in ipairs(self.field_defs) do - if field_def.field_id == v.field_id and not - ((field_def.is_nullable or field_def.is_optional) and v.elements == nil) then - elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) - if field_def.element_type ~= nil then - for i, e in ipairs(elems[field_def.name].elements) do - elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) - end - end - end - end - end - base_type_obj.elements = elems -end - -function PeriodicEnergyMeasured:read(device, endpoint_id) - return cluster_base.read( - device, - endpoint_id, - self._cluster.ID, - nil, --attribute_id - self.ID - ) -end - -function PeriodicEnergyMeasured:subscribe(device, endpoint_id) - return cluster_base.subscribe( - device, - endpoint_id, - self._cluster.ID, - nil, --attribute_id - self.ID - ) -end - -function PeriodicEnergyMeasured:build_test_event_report( - device, - endpoint_id, - fields, - status -) - local data = {} - data.elements = {} - data.num_elements = 0 - setmetatable(data, StructureABC.new_mt({NAME = "PeriodicEnergyMeasuredEventData", ID = 0x15})) - for idx, field_def in ipairs(self.field_defs) do --Note: idx is 1 when field_id is 0 - if (not field_def.is_optional and not field_def.is_nullable) and not fields[field_def.name] then - error("Missing non optional or non_nullable field: " .. field_def.name) - elseif fields[field_def.name] then - data.elements[field_def.name] = data_types.validate_or_build_type(fields[field_def.name], field_def.data_type, field_def.name) - data.elements[field_def.name].field_id = field_def.field_id - data.num_elements = data.num_elements + 1 - end - end - return cluster_base.build_test_event_report( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function PeriodicEnergyMeasured:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -function PeriodicEnergyMeasured:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -return PeriodicEnergyMeasured - diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/init.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/init.lua deleted file mode 100644 index 02b085583e..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/init.lua +++ /dev/null @@ -1,25 +0,0 @@ -local event_mt = {} -event_mt.__event_cache = {} -event_mt.__index = function(self, key) - if event_mt.__event_cache[key] == nil then - local req_loc = string.format("ElectricalEnergyMeasurement.server.events.%s", key) - local raw_def = require(req_loc) - local cluster = rawget(self, "_cluster") - raw_def:set_parent_cluster(cluster) - event_mt.__event_cache[key] = raw_def - end - return event_mt.__event_cache[key] -end - - -local ElectricalEnergyMeasurementEvents = {} - -function ElectricalEnergyMeasurementEvents:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -setmetatable(ElectricalEnergyMeasurementEvents, event_mt) - -return ElectricalEnergyMeasurementEvents - diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/Feature.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/Feature.lua deleted file mode 100644 index 717ba6a2f3..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/Feature.lua +++ /dev/null @@ -1,116 +0,0 @@ -local data_types = require "st.matter.data_types" -local UintABC = require "st.matter.data_types.base_defs.UintABC" -local Feature = {} -local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) - -Feature.BASE_MASK = 0xFFFF -Feature.IMPORTED_ENERGY = 0x0001 -Feature.EXPORTED_ENERGY = 0x0002 -Feature.CUMULATIVE_ENERGY = 0x0004 -Feature.PERIODIC_ENERGY = 0x0008 - -Feature.mask_fields = { - BASE_MASK = 0xFFFF, - IMPORTED_ENERGY = 0x0001, - EXPORTED_ENERGY = 0x0002, - CUMULATIVE_ENERGY = 0x0004, - PERIODIC_ENERGY = 0x0008, -} - -Feature.is_imported_energy_set = function(self) - return (self.value & self.IMPORTED_ENERGY) ~= 0 -end - -Feature.set_imported_energy = function(self) - if self.value ~= nil then - self.value = self.value | self.IMPORTED_ENERGY - else - self.value = self.IMPORTED_ENERGY - end -end - -Feature.unset_imported_energy = function(self) - self.value = self.value & (~self.IMPORTED_ENERGY & self.BASE_MASK) -end -Feature.is_exported_energy_set = function(self) - return (self.value & self.EXPORTED_ENERGY) ~= 0 -end - -Feature.set_exported_energy = function(self) - if self.value ~= nil then - self.value = self.value | self.EXPORTED_ENERGY - else - self.value = self.EXPORTED_ENERGY - end -end - -Feature.unset_exported_energy = function(self) - self.value = self.value & (~self.EXPORTED_ENERGY & self.BASE_MASK) -end -Feature.is_cumulative_energy_set = function(self) - return (self.value & self.CUMULATIVE_ENERGY) ~= 0 -end - -Feature.set_cumulative_energy = function(self) - if self.value ~= nil then - self.value = self.value | self.CUMULATIVE_ENERGY - else - self.value = self.CUMULATIVE_ENERGY - end -end - -Feature.unset_cumulative_energy = function(self) - self.value = self.value & (~self.CUMULATIVE_ENERGY & self.BASE_MASK) -end -Feature.is_periodic_energy_set = function(self) - return (self.value & self.PERIODIC_ENERGY) ~= 0 -end - -Feature.set_periodic_energy = function(self) - if self.value ~= nil then - self.value = self.value | self.PERIODIC_ENERGY - else - self.value = self.PERIODIC_ENERGY - end -end - -Feature.unset_periodic_energy = function(self) - self.value = self.value & (~self.PERIODIC_ENERGY & self.BASE_MASK) -end - -function Feature.bits_are_valid(feature) - local max = - Feature.IMPORTED_ENERGY | - Feature.EXPORTED_ENERGY | - Feature.CUMULATIVE_ENERGY | - Feature.PERIODIC_ENERGY - if (feature <= max) and (feature >= 1) then - return true - else - return false - end -end - -Feature.mask_methods = { - is_imported_energy_set = Feature.is_imported_energy_set, - set_imported_energy = Feature.set_imported_energy, - unset_imported_energy = Feature.unset_imported_energy, - is_exported_energy_set = Feature.is_exported_energy_set, - set_exported_energy = Feature.set_exported_energy, - unset_exported_energy = Feature.unset_exported_energy, - is_cumulative_energy_set = Feature.is_cumulative_energy_set, - set_cumulative_energy = Feature.set_cumulative_energy, - unset_cumulative_energy = Feature.unset_cumulative_energy, - is_periodic_energy_set = Feature.is_periodic_energy_set, - set_periodic_energy = Feature.set_periodic_energy, - unset_periodic_energy = Feature.unset_periodic_energy, -} - -Feature.augment_type = function(cls, val) - setmetatable(val, new_mt) -end - -setmetatable(Feature, new_mt) - -return Feature - diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/init.lua deleted file mode 100644 index 54785d16c6..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/init.lua +++ /dev/null @@ -1,94 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local ElectricalPowerMeasurementServerAttributes = require "ElectricalPowerMeasurement.server.attributes" -local ElectricalPowerMeasurementTypes = require "ElectricalPowerMeasurement.types" - -local ElectricalPowerMeasurement = {} - -ElectricalPowerMeasurement.ID = 0x0090 -ElectricalPowerMeasurement.NAME = "ElectricalPowerMeasurement" -ElectricalPowerMeasurement.server = {} -ElectricalPowerMeasurement.client = {} -ElectricalPowerMeasurement.server.attributes = ElectricalPowerMeasurementServerAttributes:set_parent_cluster(ElectricalPowerMeasurement) -ElectricalPowerMeasurement.types = ElectricalPowerMeasurementTypes - -function ElectricalPowerMeasurement:get_attribute_by_id(attr_id) - local attr_id_map = { - [0x0000] = "PowerMode", - [0x0001] = "NumberOfMeasurementTypes", - [0x0002] = "Accuracy", - [0x0003] = "Ranges", - [0x0004] = "Voltage", - [0x0005] = "ActiveCurrent", - [0x0006] = "ReactiveCurrent", - [0x0007] = "ApparentCurrent", - [0x0008] = "ActivePower", - [0x0009] = "ReactivePower", - [0x000A] = "ApparentPower", - [0x000B] = "RMSVoltage", - [0x000C] = "RMSCurrent", - [0x000D] = "RMSPower", - [0x000E] = "Frequency", - [0x000F] = "HarmonicCurrents", - [0x0010] = "HarmonicPhases", - [0x0011] = "PowerFactor", - [0x0012] = "NeutralCurrent", - [0xFFF9] = "AcceptedCommandList", - [0xFFFA] = "EventList", - [0xFFFB] = "AttributeList", - } - local attr_name = attr_id_map[attr_id] - if attr_name ~= nil then - return self.attributes[attr_name] - end - return nil -end - -ElectricalPowerMeasurement.attribute_direction_map = { - ["PowerMode"] = "server", - ["NumberOfMeasurementTypes"] = "server", - ["Accuracy"] = "server", - ["Ranges"] = "server", - ["Voltage"] = "server", - ["ActiveCurrent"] = "server", - ["ReactiveCurrent"] = "server", - ["ApparentCurrent"] = "server", - ["ActivePower"] = "server", - ["ReactivePower"] = "server", - ["ApparentPower"] = "server", - ["RMSVoltage"] = "server", - ["RMSCurrent"] = "server", - ["RMSPower"] = "server", - ["Frequency"] = "server", - ["HarmonicCurrents"] = "server", - ["HarmonicPhases"] = "server", - ["PowerFactor"] = "server", - ["NeutralCurrent"] = "server", - ["AcceptedCommandList"] = "server", - ["EventList"] = "server", - ["AttributeList"] = "server", -} - -ElectricalPowerMeasurement.FeatureMap = ElectricalPowerMeasurement.types.Feature - -function ElectricalPowerMeasurement.are_features_supported(feature, feature_map) - if (ElectricalPowerMeasurement.FeatureMap.bits_are_valid(feature)) then - return (feature & feature_map) == feature - end - return false -end - -local attribute_helper_mt = {} -attribute_helper_mt.__index = function(self, key) - local direction = ElectricalPowerMeasurement.attribute_direction_map[key] - if direction == nil then - error(string.format("Referenced unknown attribute %s on cluster %s", key, ElectricalPowerMeasurement.NAME)) - end - return ElectricalPowerMeasurement[direction].attributes[key] -end -ElectricalPowerMeasurement.attributes = {} -setmetatable(ElectricalPowerMeasurement.attributes, attribute_helper_mt) - -setmetatable(ElectricalPowerMeasurement, {__index = cluster_base}) - -return ElectricalPowerMeasurement - diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/types/Feature.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/types/Feature.lua deleted file mode 100644 index cbda4f3478..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/types/Feature.lua +++ /dev/null @@ -1,138 +0,0 @@ -local data_types = require "st.matter.data_types" -local UintABC = require "st.matter.data_types.base_defs.UintABC" - -local Feature = {} -local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) - -Feature.BASE_MASK = 0xFFFF -Feature.DIRECT_CURRENT = 0x0001 -Feature.ALTERNATING_CURRENT = 0x0002 -Feature.POLYPHASE_POWER = 0x0004 -Feature.HARMONICS = 0x0008 -Feature.POWER_QUALITY = 0x0010 - -Feature.mask_fields = { - BASE_MASK = 0xFFFF, - DIRECT_CURRENT = 0x0001, - ALTERNATING_CURRENT = 0x0002, - POLYPHASE_POWER = 0x0004, - HARMONICS = 0x0008, - POWER_QUALITY = 0x0010, -} - -Feature.is_direct_current_set = function(self) - return (self.value & self.DIRECT_CURRENT) ~= 0 -end - -Feature.set_direct_current = function(self) - if self.value ~= nil then - self.value = self.value | self.DIRECT_CURRENT - else - self.value = self.DIRECT_CURRENT - end -end - -Feature.unset_direct_current = function(self) - self.value = self.value & (~self.DIRECT_CURRENT & self.BASE_MASK) -end -Feature.is_alternating_current_set = function(self) - return (self.value & self.ALTERNATING_CURRENT) ~= 0 -end - -Feature.set_alternating_current = function(self) - if self.value ~= nil then - self.value = self.value | self.ALTERNATING_CURRENT - else - self.value = self.ALTERNATING_CURRENT - end -end - -Feature.unset_alternating_current = function(self) - self.value = self.value & (~self.ALTERNATING_CURRENT & self.BASE_MASK) -end -Feature.is_polyphase_power_set = function(self) - return (self.value & self.POLYPHASE_POWER) ~= 0 -end - -Feature.set_polyphase_power = function(self) - if self.value ~= nil then - self.value = self.value | self.POLYPHASE_POWER - else - self.value = self.POLYPHASE_POWER - end -end - -Feature.unset_polyphase_power = function(self) - self.value = self.value & (~self.POLYPHASE_POWER & self.BASE_MASK) -end -Feature.is_harmonics_set = function(self) - return (self.value & self.HARMONICS) ~= 0 -end - -Feature.set_harmonics = function(self) - if self.value ~= nil then - self.value = self.value | self.HARMONICS - else - self.value = self.HARMONICS - end -end - -Feature.unset_harmonics = function(self) - self.value = self.value & (~self.HARMONICS & self.BASE_MASK) -end -Feature.is_power_quality_set = function(self) - return (self.value & self.POWER_QUALITY) ~= 0 -end - -Feature.set_power_quality = function(self) - if self.value ~= nil then - self.value = self.value | self.POWER_QUALITY - else - self.value = self.POWER_QUALITY - end -end - -Feature.unset_power_quality = function(self) - self.value = self.value & (~self.POWER_QUALITY & self.BASE_MASK) -end - -function Feature.bits_are_valid(feature) - local max = - Feature.DIRECT_CURRENT | - Feature.ALTERNATING_CURRENT | - Feature.POLYPHASE_POWER | - Feature.HARMONICS | - Feature.POWER_QUALITY - if (feature <= max) and (feature >= 1) then - return true - else - return false - end -end - -Feature.mask_methods = { - is_direct_current_set = Feature.is_direct_current_set, - set_direct_current = Feature.set_direct_current, - unset_direct_current = Feature.unset_direct_current, - is_alternating_current_set = Feature.is_alternating_current_set, - set_alternating_current = Feature.set_alternating_current, - unset_alternating_current = Feature.unset_alternating_current, - is_polyphase_power_set = Feature.is_polyphase_power_set, - set_polyphase_power = Feature.set_polyphase_power, - unset_polyphase_power = Feature.unset_polyphase_power, - is_harmonics_set = Feature.is_harmonics_set, - set_harmonics = Feature.set_harmonics, - unset_harmonics = Feature.unset_harmonics, - is_power_quality_set = Feature.is_power_quality_set, - set_power_quality = Feature.set_power_quality, - unset_power_quality = Feature.unset_power_quality, -} - -Feature.augment_type = function(cls, val) - setmetatable(val, new_mt) -end - -setmetatable(Feature, new_mt) - -return Feature - diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/types/init.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/types/init.lua deleted file mode 100644 index 16d13a0688..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/types/init.lua +++ /dev/null @@ -1,15 +0,0 @@ -local types_mt = {} -types_mt.__types_cache = {} -types_mt.__index = function(self, key) - if types_mt.__types_cache[key] == nil then - types_mt.__types_cache[key] = require("ElectricalPowerMeasurement.types." .. key) - end - return types_mt.__types_cache[key] -end - -local ElectricalPowerMeasurementTypes = {} - -setmetatable(ElectricalPowerMeasurementTypes, types_mt) - -return ElectricalPowerMeasurementTypes - diff --git a/drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/AttributeList.lua deleted file mode 100644 index f2ca149f03..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/AttributeList.lua +++ /dev/null @@ -1,75 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" - -local AttributeList = { - ID = 0xFFFB, - NAME = "AttributeList", - base_type = require "st.matter.data_types.Array", - element_type = require "st.matter.data_types.Uint32", -} - -function AttributeList:augment_type(data_type_obj) - for i, v in ipairs(data_type_obj.elements) do - data_type_obj.elements[i] = data_types.validate_or_build_type(v, AttributeList.element_type) - end -end - -function AttributeList:new_value(...) - local o = self.base_type(table.unpack({...})) - - return o -end - -function AttributeList:read(device, endpoint_id) - return cluster_base.read( - device, - endpoint_id, - self._cluster.ID, - self.ID, - nil --event_id - ) -end - -function AttributeList:subscribe(device, endpoint_id) - return cluster_base.subscribe( - device, - endpoint_id, - self._cluster.ID, - self.ID, - nil --event_id - ) -end - -function AttributeList:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function AttributeList:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function AttributeList:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(AttributeList, {__call = AttributeList.new_value, __index = AttributeList.base_type}) -return AttributeList - diff --git a/drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua deleted file mode 100644 index 50127a3537..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua +++ /dev/null @@ -1,41 +0,0 @@ -local ConcentrationMeasurementServerAttributesLevelValue = require "ConcentrationMeasurement.server.attributes.LevelValue" - -local LevelValue = { - ID = 0x000A, - NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", -} - -function LevelValue:new_value(...) - ConcentrationMeasurementServerAttributesLevelValue:new_value(...) -end - -function LevelValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function LevelValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function LevelValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function LevelValue:deserialize(tlv_buf) - return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) -end - -setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) -return LevelValue - diff --git a/drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua deleted file mode 100644 index 763bbaa17b..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ /dev/null @@ -1,56 +0,0 @@ -local cluster_base = require "st.matter.cluster_base" -local data_types = require "st.matter.data_types" -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasuredValue = require "ConcentrationMeasurement.server.attributes.MeasuredValue" - - -local MeasuredValue = { - ID = 0x0000, - NAME = "MeasuredValue", - base_type = require "st.matter.data_types.SinglePrecisionFloat", -} - -function MeasuredValue:new_value(...) - return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) -end - -function MeasuredValue:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasuredValue:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasuredValue:build_test_report_data( - device, - endpoint_id, - value, - status -) - local data = data_types.validate_or_build_type(value, self.base_type) - - return cluster_base.build_test_report_data( - device, - endpoint_id, - self._cluster.ID, - self.ID, - data, - status - ) -end - -function MeasuredValue:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - - return data -end - -setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) -return MeasuredValue - diff --git a/drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua deleted file mode 100644 index d18f14b09f..0000000000 --- a/drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ /dev/null @@ -1,45 +0,0 @@ -local TLVParser = require "st.matter.TLV.TLVParser" -local ConcentrationMeasurementServerAttributesMeasurementUnit = require "ConcentrationMeasurement.server.attributes.MeasurementUnit" - - -local MeasurementUnit = { - ID = 0x0008, - NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", -} - -function MeasurementUnit:new_value(...) - return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) -end - -function MeasurementUnit:read(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:subscribe(device, endpoint_id) - return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) -end - -function MeasurementUnit:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function MeasurementUnit:build_test_report_data( - device, - endpoint_id, - value, - status -) - return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) -end - -function MeasurementUnit:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) -return MeasurementUnit - diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/init.lua similarity index 90% rename from drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/init.lua index 3fa5f37100..7bfdfd7248 100644 --- a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/init.lua @@ -1,7 +1,10 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local ActivatedCarbonFilterMonitoringServerAttributes = require "ActivatedCarbonFilterMonitoring.server.attributes" -local ActivatedCarbonFilterMonitoringServerCommands = require "ActivatedCarbonFilterMonitoring.server.commands" -local ActivatedCarbonFilterMonitoringTypes = require "ActivatedCarbonFilterMonitoring.types" +local ActivatedCarbonFilterMonitoringServerAttributes = require "embedded_clusters.ActivatedCarbonFilterMonitoring.server.attributes" +local ActivatedCarbonFilterMonitoringServerCommands = require "embedded_clusters.ActivatedCarbonFilterMonitoring.server.commands" +local ActivatedCarbonFilterMonitoringTypes = require "embedded_clusters.ActivatedCarbonFilterMonitoring.types" local ActivatedCarbonFilterMonitoring = {} diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua similarity index 88% rename from drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua index 955b89eb88..df65143e66 100644 --- a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local ChangeIndication = { ID = 0x0002, NAME = "ChangeIndication", - base_type = require "HepaFilterMonitoring.types.ChangeIndicationEnum", + base_type = require "embedded_clusters.ActivatedCarbonFilterMonitoring.types.ChangeIndicationEnum", } function ChangeIndication:new_value(...) diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/Condition.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/attributes/Condition.lua similarity index 89% rename from drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/Condition.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/attributes/Condition.lua index e668aa4c48..361e423a1c 100644 --- a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/Condition.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/attributes/Condition.lua @@ -1,4 +1,7 @@ -local cluster_base = require "st.matter.cluster_base" +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/attributes/init.lua index a02378a50d..1ecdf6a931 100644 --- a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("ActivatedCarbonFilterMonitoring.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.ActivatedCarbonFilterMonitoring.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/commands/ResetCondition.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/commands/ResetCondition.lua similarity index 96% rename from drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/commands/ResetCondition.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/commands/ResetCondition.lua index 040ce653b4..1ac942f780 100644 --- a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/commands/ResetCondition.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/commands/ResetCondition.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/commands/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/commands/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/commands/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/commands/init.lua index 7f641481d4..ec343da578 100644 --- a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/commands/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/server/commands/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local command_mt = {} command_mt.__command_cache = {} command_mt.__index = function(self, key) if command_mt.__command_cache[key] == nil then - local req_loc = string.format("ActivatedCarbonFilterMonitoring.server.commands.%s", key) + local req_loc = string.format("embedded_clusters.ActivatedCarbonFilterMonitoring.server.commands.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/types/ChangeIndicationEnum.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/types/ChangeIndicationEnum.lua similarity index 92% rename from drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/types/ChangeIndicationEnum.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/types/ChangeIndicationEnum.lua index 438de24c94..6b5ab62494 100644 --- a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/types/ChangeIndicationEnum.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/types/ChangeIndicationEnum.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/types/Feature.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/types/Feature.lua similarity index 96% rename from drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/types/Feature.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/types/Feature.lua index 88474d1b0f..906e769676 100644 --- a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/types/Feature.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/types/Feature.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/types/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/types/init.lua similarity index 62% rename from drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/types/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/types/init.lua index 2ff8e6e89a..7cdc2d1561 100644 --- a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/types/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ActivatedCarbonFilterMonitoring/types/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) if types_mt.__types_cache[key] == nil then - types_mt.__types_cache[key] = require("ActivatedCarbonFilterMonitoring.types." .. key) + types_mt.__types_cache[key] = require("embedded_clusters.ActivatedCarbonFilterMonitoring.types." .. key) end return types_mt.__types_cache[key] end diff --git a/drivers/SmartThings/matter-thermostat/src/AirQuality/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/init.lua similarity index 86% rename from drivers/SmartThings/matter-thermostat/src/AirQuality/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/init.lua index 78e77fce51..4f6d19cc9f 100644 --- a/drivers/SmartThings/matter-thermostat/src/AirQuality/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local AirQualityServerAttributes = require "AirQuality.server.attributes" -local AirQualityTypes = require "AirQuality.types" +local AirQualityServerAttributes = require "embedded_clusters.AirQuality.server.attributes" +local AirQualityTypes = require "embedded_clusters.AirQuality.types" local AirQuality = {} diff --git a/drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/AcceptedCommandList.lua similarity index 94% rename from drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/AcceptedCommandList.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/AcceptedCommandList.lua index 6a8d95df1d..a1e56c6597 100644 --- a/drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/AcceptedCommandList.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/AcceptedCommandList.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/AirQuality.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/AirQuality.lua similarity index 88% rename from drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/AirQuality.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/AirQuality.lua index f92f43543a..1beb6218f9 100644 --- a/drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/AirQuality.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/AirQuality.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local AirQuality = { ID = 0x0000, NAME = "AirQuality", - base_type = require "AirQuality.types.AirQualityEnum", + base_type = require "embedded_clusters.AirQuality.types.AirQualityEnum", } function AirQuality:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/AttributeList.lua similarity index 94% rename from drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/AttributeList.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/AttributeList.lua index 93e96817e6..238b50ade3 100644 --- a/drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/AttributeList.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/AttributeList.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/EventList.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/EventList.lua similarity index 94% rename from drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/EventList.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/EventList.lua index 69155cd7ca..719f17a231 100644 --- a/drivers/SmartThings/matter-thermostat/src/AirQuality/server/attributes/EventList.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/EventList.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/init.lua similarity index 75% rename from drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/init.lua index aef3b476a9..50295b081a 100644 --- a/drivers/SmartThings/matter-sensor/src/AirQuality/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("AirQuality.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.AirQuality.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/AirQuality/types/AirQualityEnum.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/types/AirQualityEnum.lua similarity index 94% rename from drivers/SmartThings/matter-sensor/src/AirQuality/types/AirQualityEnum.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/types/AirQualityEnum.lua index 317a42dc9b..c2c255614a 100644 --- a/drivers/SmartThings/matter-sensor/src/AirQuality/types/AirQualityEnum.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/types/AirQualityEnum.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-thermostat/src/AirQuality/types/Feature.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/types/Feature.lua similarity index 96% rename from drivers/SmartThings/matter-thermostat/src/AirQuality/types/Feature.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/types/Feature.lua index 86b90ce627..906a09a2bb 100644 --- a/drivers/SmartThings/matter-thermostat/src/AirQuality/types/Feature.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/types/Feature.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-sensor/src/AirQuality/types/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/types/init.lua similarity index 60% rename from drivers/SmartThings/matter-sensor/src/AirQuality/types/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/types/init.lua index 88a2b861b7..b77d67de82 100644 --- a/drivers/SmartThings/matter-sensor/src/AirQuality/types/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/AirQuality/types/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) if types_mt.__types_cache[key] == nil then - types_mt.__types_cache[key] = require("AirQuality.types." .. key) + types_mt.__types_cache[key] = require("embedded_clusters.AirQuality.types." .. key) end return types_mt.__types_cache[key] end diff --git a/drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/init.lua similarity index 88% rename from drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/init.lua index f5109f8943..4de97147e4 100644 --- a/drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local CarbonDioxideConcentrationMeasurementServerAttributes = require "CarbonDioxideConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local CarbonDioxideConcentrationMeasurementServerAttributes = require "embedded_clusters.CarbonDioxideConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local CarbonDioxideConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/init.lua index 93583c2080..0206213e6f 100644 --- a/drivers/SmartThings/matter-sensor/src/CarbonDioxideConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonDioxideConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("CarbonDioxideConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.CarbonDioxideConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/init.lua similarity index 88% rename from drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/init.lua index e8cdb487f5..a6e1f24d1d 100644 --- a/drivers/SmartThings/matter-sensor/src/CarbonMonoxideConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local CarbonMonoxideConcentrationMeasurementServerAttributes = require "CarbonMonoxideConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local CarbonMonoxideConcentrationMeasurementServerAttributes = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local CarbonMonoxideConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/init.lua index 2307e8977d..1a7e7b508c 100644 --- a/drivers/SmartThings/matter-thermostat/src/CarbonMonoxideConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/CarbonMonoxideConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("CarbonMonoxideConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.CarbonMonoxideConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/init.lua similarity index 92% rename from drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/init.lua index 596d1bfe80..cb4cffa2d2 100644 --- a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local ConcentrationMeasurementServerAttributes = require "ConcentrationMeasurement.server.attributes" -local ConcentrationMeasurementTypes = require "ConcentrationMeasurement.types" +local ConcentrationMeasurementServerAttributes = require "embedded_clusters.ConcentrationMeasurement.server.attributes" +local ConcentrationMeasurementTypes = require "embedded_clusters.ConcentrationMeasurement.types" local ConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/server/attributes/LevelValue.lua similarity index 88% rename from drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/server/attributes/LevelValue.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/server/attributes/LevelValue.lua index e7023a336c..e4f88c8491 100644 --- a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/server/attributes/LevelValue.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/server/attributes/LevelValue.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local LevelValue = { ID = 0x000A, NAME = "LevelValue", - base_type = require "ConcentrationMeasurement.types.LevelValueEnum", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", } function LevelValue:new_value(...) diff --git a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/server/attributes/MeasuredValue.lua similarity index 93% rename from drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/server/attributes/MeasuredValue.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/server/attributes/MeasuredValue.lua index c658d2d3aa..2ab739841f 100644 --- a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/server/attributes/MeasuredValue.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/server/attributes/MeasurementUnit.lua similarity index 88% rename from drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/server/attributes/MeasurementUnit.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/server/attributes/MeasurementUnit.lua index 3d50e8b97b..0fa14745c0 100644 --- a/drivers/SmartThings/matter-sensor/src/ConcentrationMeasurement/server/attributes/MeasurementUnit.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local MeasurementUnit = { ID = 0x0008, NAME = "MeasurementUnit", - base_type = require "ConcentrationMeasurement.types.MeasurementUnitEnum", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", } function MeasurementUnit:new_value(...) diff --git a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/server/attributes/init.lua index a1a0092151..19cde9aa55 100644 --- a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("ConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.ConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/types/Feature.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/types/Feature.lua similarity index 98% rename from drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/types/Feature.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/types/Feature.lua index 9aa2413903..0bb19bec62 100644 --- a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/types/Feature.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/types/Feature.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/types/LevelValueEnum.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/types/LevelValueEnum.lua similarity index 93% rename from drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/types/LevelValueEnum.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/types/LevelValueEnum.lua index b1264d72b8..02b4f727df 100644 --- a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/types/LevelValueEnum.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/types/LevelValueEnum.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/types/MeasurementUnitEnum.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/types/MeasurementUnitEnum.lua similarity index 94% rename from drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/types/MeasurementUnitEnum.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/types/MeasurementUnitEnum.lua index c8302c5cc4..6efd90901a 100644 --- a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/types/MeasurementUnitEnum.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/types/MeasurementUnitEnum.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/types/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/types/init.lua similarity index 62% rename from drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/types/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/types/init.lua index f6da1e6b62..c339f17414 100644 --- a/drivers/SmartThings/matter-thermostat/src/ConcentrationMeasurement/types/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ConcentrationMeasurement/types/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) if types_mt.__types_cache[key] == nil then - types_mt.__types_cache[key] = require("ConcentrationMeasurement.types." .. key) + types_mt.__types_cache[key] = require("embedded_clusters.ConcentrationMeasurement.types." .. key) end return types_mt.__types_cache[key] end diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/init.lua similarity index 61% rename from drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/init.lua index bc5685ae75..0f564673a9 100644 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/init.lua @@ -1,7 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local ElectricalEnergyMeasurementServerAttributes = require "ElectricalEnergyMeasurement.server.attributes" -local ElectricalEnergyMeasurementEvents = require "ElectricalEnergyMeasurement.server.events" -local ElectricalEnergyMeasurementTypes = require "ElectricalEnergyMeasurement.types" +local ElectricalEnergyMeasurementServerAttributes = require "embedded_clusters.ElectricalEnergyMeasurement.server.attributes" +local ElectricalEnergyMeasurementTypes = require "embedded_clusters.ElectricalEnergyMeasurement.types" local ElectricalEnergyMeasurement = {} ElectricalEnergyMeasurement.ID = 0x0091 @@ -9,20 +11,12 @@ ElectricalEnergyMeasurement.NAME = "ElectricalEnergyMeasurement" ElectricalEnergyMeasurement.server = {} ElectricalEnergyMeasurement.client = {} ElectricalEnergyMeasurement.server.attributes = ElectricalEnergyMeasurementServerAttributes:set_parent_cluster(ElectricalEnergyMeasurement) -ElectricalEnergyMeasurement.server.events = ElectricalEnergyMeasurementEvents:set_parent_cluster(ElectricalEnergyMeasurement) ElectricalEnergyMeasurement.types = ElectricalEnergyMeasurementTypes function ElectricalEnergyMeasurement:get_attribute_by_id(attr_id) local attr_id_map = { - [0x0000] = "Accuracy", [0x0001] = "CumulativeEnergyImported", - [0x0002] = "CumulativeEnergyExported", [0x0003] = "PeriodicEnergyImported", - [0x0004] = "PeriodicEnergyExported", - [0x0005] = "CumulativeEnergyReset", - [0xFFF9] = "AcceptedCommandList", - [0xFFFA] = "EventList", - [0xFFFB] = "AttributeList", } local attr_name = attr_id_map[attr_id] if attr_name ~= nil then @@ -32,15 +26,8 @@ function ElectricalEnergyMeasurement:get_attribute_by_id(attr_id) end ElectricalEnergyMeasurement.attribute_direction_map = { - ["Accuracy"] = "server", ["CumulativeEnergyImported"] = "server", - ["CumulativeEnergyExported"] = "server", ["PeriodicEnergyImported"] = "server", - ["PeriodicEnergyExported"] = "server", - ["CumulativeEnergyReset"] = "server", - ["AcceptedCommandList"] = "server", - ["EventList"] = "server", - ["AttributeList"] = "server", } ElectricalEnergyMeasurement.FeatureMap = ElectricalEnergyMeasurement.types.Feature @@ -63,13 +50,6 @@ end ElectricalEnergyMeasurement.attributes = {} setmetatable(ElectricalEnergyMeasurement.attributes, attribute_helper_mt) -local event_helper_mt = {} -event_helper_mt.__index = function(self, key) - return ElectricalEnergyMeasurement.server.events[key] -end -ElectricalEnergyMeasurement.events = {} -setmetatable(ElectricalEnergyMeasurement.events, event_helper_mt) - setmetatable(ElectricalEnergyMeasurement, {__index = cluster_base}) return ElectricalEnergyMeasurement diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua similarity index 88% rename from drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua index 3dc58635e1..2d41790440 100644 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local CumulativeEnergyImported = { ID = 0x0001, NAME = "CumulativeEnergyImported", - base_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", + base_type = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", } function CumulativeEnergyImported:new_value(...) diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua similarity index 88% rename from drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua index 753b91ea2d..5daccf48ab 100644 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local PeriodicEnergyImported = { ID = 0x0003, NAME = "PeriodicEnergyImported", - base_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", + base_type = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", } function PeriodicEnergyImported:new_value(...) diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/init.lua index adfdf42bbf..57bc0d1f72 100644 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("ElectricalEnergyMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.ElectricalEnergyMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua similarity index 97% rename from drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua index 950b260227..a4c58a3646 100644 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local StructureABC = require "st.matter.data_types.base_defs.StructureABC" local EnergyMeasurementStruct = {} diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/types/Feature.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/types/Feature.lua new file mode 100644 index 0000000000..e3db76f49d --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/types/Feature.lua @@ -0,0 +1,31 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.IMPORTED_ENERGY = 0x0001 +Feature.EXPORTED_ENERGY = 0x0002 +Feature.CUMULATIVE_ENERGY = 0x0004 +Feature.PERIODIC_ENERGY = 0x0008 + +function Feature.bits_are_valid(feature) + local max = + Feature.IMPORTED_ENERGY | + Feature.EXPORTED_ENERGY | + Feature.CUMULATIVE_ENERGY | + Feature.PERIODIC_ENERGY + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +setmetatable(Feature, new_mt) + +return Feature + diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/types/init.lua similarity index 62% rename from drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/types/init.lua index bb0c39fe0e..ec29b53e05 100644 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalEnergyMeasurement/types/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) if types_mt.__types_cache[key] == nil then - types_mt.__types_cache[key] = require("ElectricalEnergyMeasurement.types." .. key) + types_mt.__types_cache[key] = require("embedded_clusters.ElectricalEnergyMeasurement.types." .. key) end return types_mt.__types_cache[key] end diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalPowerMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalPowerMeasurement/init.lua new file mode 100644 index 0000000000..c9061e3cc4 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalPowerMeasurement/init.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local ElectricalPowerMeasurementServerAttributes = require "embedded_clusters.ElectricalPowerMeasurement.server.attributes" + +local ElectricalPowerMeasurement = {} + +ElectricalPowerMeasurement.ID = 0x0090 +ElectricalPowerMeasurement.NAME = "ElectricalPowerMeasurement" +ElectricalPowerMeasurement.server = {} +ElectricalPowerMeasurement.client = {} +ElectricalPowerMeasurement.server.attributes = ElectricalPowerMeasurementServerAttributes:set_parent_cluster(ElectricalPowerMeasurement) + +function ElectricalPowerMeasurement:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0008] = "ActivePower", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +ElectricalPowerMeasurement.attribute_direction_map = { + ["ActivePower"] = "server", +} + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = ElectricalPowerMeasurement.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, ElectricalPowerMeasurement.NAME)) + end + return ElectricalPowerMeasurement[direction].attributes[key] +end +ElectricalPowerMeasurement.attributes = {} +setmetatable(ElectricalPowerMeasurement.attributes, attribute_helper_mt) + +setmetatable(ElectricalPowerMeasurement, {__index = cluster_base}) + +return ElectricalPowerMeasurement + diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalPowerMeasurement/server/attributes/ActivePower.lua similarity index 93% rename from drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalPowerMeasurement/server/attributes/ActivePower.lua index 6c34abd2f4..f1696509f5 100644 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalPowerMeasurement/server/attributes/ActivePower.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -65,4 +68,3 @@ end setmetatable(ActivePower, {__call = ActivePower.new_value, __index = ActivePower.base_type}) return ActivePower - diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalPowerMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalPowerMeasurement/server/attributes/init.lua index 0c30fa8dd4..6de69b94ff 100644 --- a/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/ElectricalPowerMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("ElectricalPowerMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.ElectricalPowerMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/init.lua similarity index 88% rename from drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/init.lua index 5920a9dc66..cdfd1d597e 100644 --- a/drivers/SmartThings/matter-sensor/src/FormaldehydeConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local FormaldehydeConcentrationMeasurementServerAttributes = require "FormaldehydeConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local FormaldehydeConcentrationMeasurementServerAttributes = require "embedded_clusters.FormaldehydeConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local FormaldehydeConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/init.lua index 37900b0fb1..3dee13abe3 100644 --- a/drivers/SmartThings/matter-thermostat/src/FormaldehydeConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/FormaldehydeConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("FormaldehydeConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.FormaldehydeConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/init.lua similarity index 89% rename from drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/init.lua index 84400d7833..7a36d3aa92 100644 --- a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/init.lua @@ -1,7 +1,10 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local HepaFilterMonitoringServerAttributes = require "HepaFilterMonitoring.server.attributes" -local HepaFilterMonitoringServerCommands = require "HepaFilterMonitoring.server.commands" -local HepaFilterMonitoringTypes = require "HepaFilterMonitoring.types" +local HepaFilterMonitoringServerAttributes = require "embedded_clusters.HepaFilterMonitoring.server.attributes" +local HepaFilterMonitoringServerCommands = require "embedded_clusters.HepaFilterMonitoring.server.commands" +local HepaFilterMonitoringTypes = require "embedded_clusters.HepaFilterMonitoring.types" local HepaFilterMonitoring = {} diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/attributes/ChangeIndication.lua similarity index 88% rename from drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/attributes/ChangeIndication.lua index 4980356485..9dca020204 100644 --- a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/attributes/ChangeIndication.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -5,7 +8,7 @@ local TLVParser = require "st.matter.TLV.TLVParser" local ChangeIndication = { ID = 0x0002, NAME = "ChangeIndication", - base_type = require "ActivatedCarbonFilterMonitoring.types.ChangeIndicationEnum", + base_type = require "embedded_clusters.HepaFilterMonitoring.types.ChangeIndicationEnum", } function ChangeIndication:new_value(...) diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/Condition.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/attributes/Condition.lua similarity index 93% rename from drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/Condition.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/attributes/Condition.lua index e668aa4c48..76a4fd9760 100644 --- a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/Condition.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/attributes/Condition.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/attributes/init.lua index 8d7ffe6c00..2590980846 100644 --- a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("HepaFilterMonitoring.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.HepaFilterMonitoring.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/commands/ResetCondition.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/commands/ResetCondition.lua similarity index 96% rename from drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/commands/ResetCondition.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/commands/ResetCondition.lua index 040ce653b4..1ac942f780 100644 --- a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/commands/ResetCondition.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/commands/ResetCondition.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/commands/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/commands/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/commands/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/commands/init.lua index 55a4ea7a30..77ae141f77 100644 --- a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/commands/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/server/commands/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local command_mt = {} command_mt.__command_cache = {} command_mt.__index = function(self, key) if command_mt.__command_cache[key] == nil then - local req_loc = string.format("HepaFilterMonitoring.server.commands.%s", key) + local req_loc = string.format("embedded_clusters.HepaFilterMonitoring.server.commands.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/types/ChangeIndicationEnum.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/types/ChangeIndicationEnum.lua similarity index 92% rename from drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/types/ChangeIndicationEnum.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/types/ChangeIndicationEnum.lua index 438de24c94..6b5ab62494 100644 --- a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/types/ChangeIndicationEnum.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/types/ChangeIndicationEnum.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/types/Feature.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/types/Feature.lua similarity index 96% rename from drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/types/Feature.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/types/Feature.lua index 88474d1b0f..906e769676 100644 --- a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/types/Feature.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/types/Feature.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/types/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/types/init.lua similarity index 61% rename from drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/types/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/types/init.lua index 77aca088ff..6c36af1aec 100644 --- a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/types/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/HepaFilterMonitoring/types/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) if types_mt.__types_cache[key] == nil then - types_mt.__types_cache[key] = require("HepaFilterMonitoring.types." .. key) + types_mt.__types_cache[key] = require("embedded_clusters.HepaFilterMonitoring.types." .. key) end return types_mt.__types_cache[key] end diff --git a/drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/init.lua similarity index 88% rename from drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/init.lua index b60e71050a..eae9be65f0 100644 --- a/drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local NitrogenDioxideConcentrationMeasurementServerAttributes = require "NitrogenDioxideConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local NitrogenDioxideConcentrationMeasurementServerAttributes = require "embedded_clusters.NitrogenDioxideConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local NitrogenDioxideConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/init.lua index 06c3d6dd55..c82517d362 100644 --- a/drivers/SmartThings/matter-thermostat/src/NitrogenDioxideConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/NitrogenDioxideConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("NitrogenDioxideConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.NitrogenDioxideConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/init.lua similarity index 85% rename from drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/init.lua index 83fd04857e..c49ea94b39 100644 --- a/drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local OzoneConcentrationMeasurementServerAttributes = require "OzoneConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local OzoneConcentrationMeasurementServerAttributes = require "embedded_clusters.OzoneConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local OzoneConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/init.lua index fe0048cd99..918b680495 100644 --- a/drivers/SmartThings/matter-thermostat/src/OzoneConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/OzoneConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("OzoneConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.OzoneConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/init.lua similarity index 85% rename from drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/init.lua index 98eebd407e..3b333b5417 100644 --- a/drivers/SmartThings/matter-sensor/src/Pm10ConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local Pm10ConcentrationMeasurementServerAttributes = require "Pm10ConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local Pm10ConcentrationMeasurementServerAttributes = require "embedded_clusters.Pm10ConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local Pm10ConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/init.lua index 55f08c7e43..3b1e6617a4 100644 --- a/drivers/SmartThings/matter-thermostat/src/Pm10ConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm10ConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("Pm10ConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.Pm10ConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/init.lua similarity index 85% rename from drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/init.lua index 0b3caa3bd2..b2e6656a9a 100644 --- a/drivers/SmartThings/matter-sensor/src/Pm1ConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local Pm1ConcentrationMeasurementServerAttributes = require "Pm1ConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local Pm1ConcentrationMeasurementServerAttributes = require "embedded_clusters.Pm1ConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local Pm1ConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/init.lua index f668e41a07..2635da32a6 100644 --- a/drivers/SmartThings/matter-thermostat/src/Pm1ConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm1ConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("Pm1ConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.Pm1ConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/init.lua similarity index 85% rename from drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/init.lua index 5234346d60..e6e6144f94 100644 --- a/drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local Pm25ConcentrationMeasurementServerAttributes = require "Pm25ConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local Pm25ConcentrationMeasurementServerAttributes = require "embedded_clusters.Pm25ConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local Pm25ConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/init.lua index 2c7d5fce7b..5c432da0ec 100644 --- a/drivers/SmartThings/matter-thermostat/src/Pm25ConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/Pm25ConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("Pm25ConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.Pm25ConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/init.lua similarity index 85% rename from drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/init.lua index 2a4cc04a0d..3d4b21d602 100644 --- a/drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local RadonConcentrationMeasurementServerAttributes = require "RadonConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local RadonConcentrationMeasurementServerAttributes = require "embedded_clusters.RadonConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local RadonConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/init.lua index b83ef67bfc..8aa225e510 100644 --- a/drivers/SmartThings/matter-thermostat/src/RadonConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/RadonConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("RadonConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.RadonConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/init.lua similarity index 89% rename from drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/init.lua index a99c1dea50..cad49a14e1 100644 --- a/drivers/SmartThings/matter-sensor/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/init.lua @@ -1,6 +1,9 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local TotalVolatileOrganicCompoundsConcentrationMeasurementServerAttributes = require "TotalVolatileOrganicCompoundsConcentrationMeasurement.server.attributes" -local ConcentrationMeasurement = require "ConcentrationMeasurement" +local TotalVolatileOrganicCompoundsConcentrationMeasurementServerAttributes = require "embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.server.attributes" +local ConcentrationMeasurement = require "embedded_clusters.ConcentrationMeasurement" local TotalVolatileOrganicCompoundsConcentrationMeasurement = {} diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua new file mode 100644 index 0000000000..cc8f7b47eb --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/LevelValue.lua @@ -0,0 +1,44 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ConcentrationMeasurementServerAttributesLevelValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.LevelValue" + +local LevelValue = { + ID = 0x000A, + NAME = "LevelValue", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.LevelValueEnum", +} + +function LevelValue:new_value(...) + ConcentrationMeasurementServerAttributesLevelValue:new_value(...) +end + +function LevelValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:read(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesLevelValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function LevelValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function LevelValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesLevelValue:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function LevelValue:deserialize(tlv_buf) + return ConcentrationMeasurementServerAttributesLevelValue:deserialize(tlv_buf) +end + +setmetatable(LevelValue, {__call = LevelValue.new_value, __index = LevelValue.base_type}) +return LevelValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..ca0dafb0dc --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,59 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasuredValue = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasuredValue" + + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.SinglePrecisionFloat", +} + +function MeasuredValue:new_value(...) + return ConcentrationMeasurementServerAttributesMeasuredValue:new_value(...) +end + +function MeasuredValue:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:read(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasuredValue:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua new file mode 100644 index 0000000000..9597906d3a --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/MeasurementUnit.lua @@ -0,0 +1,48 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local TLVParser = require "st.matter.TLV.TLVParser" +local ConcentrationMeasurementServerAttributesMeasurementUnit = require "embedded_clusters.ConcentrationMeasurement.server.attributes.MeasurementUnit" + + +local MeasurementUnit = { + ID = 0x0008, + NAME = "MeasurementUnit", + base_type = require "embedded_clusters.ConcentrationMeasurement.types.MeasurementUnitEnum", +} + +function MeasurementUnit:new_value(...) + return ConcentrationMeasurementServerAttributesMeasurementUnit:new_value(...) +end + +function MeasurementUnit:read(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:read(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:subscribe(device, endpoint_id) + return ConcentrationMeasurementServerAttributesMeasurementUnit:subscribe(device, endpoint_id, self._cluster.ID) +end + +function MeasurementUnit:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MeasurementUnit:build_test_report_data( + device, + endpoint_id, + value, + status +) + return ConcentrationMeasurementServerAttributesMeasurementUnit:build_test_report_data(device, endpoint_id, value, status, self._cluster.ID) +end + +function MeasurementUnit:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MeasurementUnit, {__call = MeasurementUnit.new_value, __index = MeasurementUnit.base_type}) +return MeasurementUnit + diff --git a/drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/init.lua index c0c1b65c37..b67cbcd7b6 100644 --- a/drivers/SmartThings/matter-thermostat/src/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("TotalVolatileOrganicCompoundsConcentrationMeasurement.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/init.lua similarity index 87% rename from drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/init.lua index 1155cfd636..3a1c2f1bfb 100644 --- a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/init.lua @@ -1,7 +1,10 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" -local WaterHeaterModeServerAttributes = require "WaterHeaterMode.server.attributes" -local WaterHeaterModeServerCommands = require "WaterHeaterMode.server.commands" -local WaterHeaterModeTypes = require "WaterHeaterMode.types" +local WaterHeaterModeServerAttributes = require "embedded_clusters.WaterHeaterMode.server.attributes" +local WaterHeaterModeServerCommands = require "embedded_clusters.WaterHeaterMode.server.commands" +local WaterHeaterModeTypes = require "embedded_clusters.WaterHeaterMode.types" local WaterHeaterMode = {} diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/CurrentMode.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/attributes/CurrentMode.lua similarity index 93% rename from drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/CurrentMode.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/attributes/CurrentMode.lua index aa20156f74..165b906df5 100644 --- a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/CurrentMode.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/attributes/CurrentMode.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -24,7 +27,6 @@ function CurrentMode:read(device, endpoint_id) ) end - function CurrentMode:subscribe(device, endpoint_id) return cluster_base.subscribe( device, diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/SupportedModes.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/attributes/SupportedModes.lua similarity index 90% rename from drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/SupportedModes.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/attributes/SupportedModes.lua index 1f393a17d4..903a374d43 100644 --- a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/SupportedModes.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/attributes/SupportedModes.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" @@ -6,7 +9,7 @@ local SupportedModes = { ID = 0x0000, NAME = "SupportedModes", base_type = require "st.matter.data_types.Array", - element_type = require "WaterHeaterMode.types.ModeOptionStruct", + element_type = require "embedded_clusters.WaterHeaterMode.types.ModeOptionStruct", } function SupportedModes:augment_type(data_type_obj) diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/attributes/init.lua similarity index 75% rename from drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/attributes/init.lua index 020a4125ce..fb7bed9828 100644 --- a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/attributes/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local attr_mt = {} attr_mt.__attr_cache = {} attr_mt.__index = function(self, key) if attr_mt.__attr_cache[key] == nil then - local req_loc = string.format("WaterHeaterMode.server.attributes.%s", key) + local req_loc = string.format("embedded_clusters.WaterHeaterMode.server.attributes.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/commands/ChangeToMode.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/commands/ChangeToMode.lua similarity index 96% rename from drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/commands/ChangeToMode.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/commands/ChangeToMode.lua index 73ddbd1029..726352b448 100644 --- a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/commands/ChangeToMode.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/commands/ChangeToMode.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/commands/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/commands/init.lua similarity index 76% rename from drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/commands/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/commands/init.lua index 9736c4577f..64660ed336 100644 --- a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/commands/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/server/commands/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local command_mt = {} command_mt.__command_cache = {} command_mt.__index = function(self, key) if command_mt.__command_cache[key] == nil then - local req_loc = string.format("WaterHeaterMode.server.commands.%s", key) + local req_loc = string.format("embedded_clusters.WaterHeaterMode.server.commands.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/Feature.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/Feature.lua similarity index 92% rename from drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/Feature.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/Feature.lua index da49bf4115..c7e987399d 100644 --- a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/Feature.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/Feature.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeOptionStruct.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/ModeOptionStruct.lua similarity index 94% rename from drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeOptionStruct.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/ModeOptionStruct.lua index f770a7916c..1db8c5b634 100644 --- a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeOptionStruct.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/ModeOptionStruct.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local StructureABC = require "st.matter.data_types.base_defs.StructureABC" @@ -24,7 +27,7 @@ ModeOptionStruct.field_defs = { is_nullable = false, is_optional = false, data_type = require "st.matter.data_types.Array", - element_type = require "WaterHeaterMode.types.ModeTagStruct", + element_type = require "embedded_clusters.WaterHeaterMode.types.ModeTagStruct", }, } diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeTag.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/ModeTag.lua similarity index 90% rename from drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeTag.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/ModeTag.lua index 009e70a40e..7d37d4374d 100644 --- a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeTag.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/ModeTag.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeTagStruct.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/ModeTagStruct.lua similarity index 96% rename from drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeTagStruct.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/ModeTagStruct.lua index 1c41eb320e..17dfd269d6 100644 --- a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeTagStruct.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/ModeTagStruct.lua @@ -1,3 +1,6 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.matter.data_types" local StructureABC = require "st.matter.data_types.base_defs.StructureABC" diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/init.lua b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/init.lua similarity index 61% rename from drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/init.lua rename to drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/init.lua index f0198ff8a0..bae583dfb4 100644 --- a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded_clusters/WaterHeaterMode/types/init.lua @@ -1,8 +1,11 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) if types_mt.__types_cache[key] == nil then - types_mt.__types_cache[key] = require("WaterHeaterMode.types." .. key) + types_mt.__types_cache[key] = require("embedded_clusters.WaterHeaterMode.types." .. key) end return types_mt.__types_cache[key] end diff --git a/drivers/SmartThings/matter-thermostat/src/init.lua b/drivers/SmartThings/matter-thermostat/src/init.lua index 58f0d75344..54cb3318f6 100644 --- a/drivers/SmartThings/matter-thermostat/src/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/init.lua @@ -1,2263 +1,515 @@ --- Copyright 2022 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -local capabilities = require "st.capabilities" local log = require "log" -local clusters = require "st.matter.clusters" -local embedded_cluster_utils = require "embedded-cluster-utils" -local im = require "st.matter.interaction_model" - -local MatterDriver = require "st.matter.driver" -local utils = require "st.utils" - -local SUPPORTED_COMPONENT_CAPABILITIES = "__supported_component_capabilities" --- declare match_profile function for use throughout file -local match_profile - --- Include driver-side definitions when lua libs api version is < 10 -local version = require "version" -if version.api < 10 then - clusters.HepaFilterMonitoring = require "HepaFilterMonitoring" - clusters.ActivatedCarbonFilterMonitoring = require "ActivatedCarbonFilterMonitoring" - clusters.AirQuality = require "AirQuality" - clusters.CarbonMonoxideConcentrationMeasurement = require "CarbonMonoxideConcentrationMeasurement" - clusters.CarbonDioxideConcentrationMeasurement = require "CarbonDioxideConcentrationMeasurement" - clusters.FormaldehydeConcentrationMeasurement = require "FormaldehydeConcentrationMeasurement" - clusters.NitrogenDioxideConcentrationMeasurement = require "NitrogenDioxideConcentrationMeasurement" - clusters.OzoneConcentrationMeasurement = require "OzoneConcentrationMeasurement" - clusters.Pm1ConcentrationMeasurement = require "Pm1ConcentrationMeasurement" - clusters.Pm10ConcentrationMeasurement = require "Pm10ConcentrationMeasurement" - clusters.Pm25ConcentrationMeasurement = require "Pm25ConcentrationMeasurement" - clusters.RadonConcentrationMeasurement = require "RadonConcentrationMeasurement" - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "TotalVolatileOrganicCompoundsConcentrationMeasurement" - -- new modes add in Matter 1.2 - clusters.Thermostat.types.ThermostatSystemMode.DRY = 0x8 - clusters.Thermostat.types.ThermostatSystemMode.SLEEP = 0x9 -end - -local SAVED_SYSTEM_MODE_IB = "__saved_system_mode_ib" -local DISALLOWED_THERMOSTAT_MODES = "__DISALLOWED_CONTROL_OPERATIONS" -local OPTIONAL_THERMOSTAT_MODES_SEEN = "__OPTIONAL_THERMOSTAT_MODES_SEEN" - -if version.api < 11 then - clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" - clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" -end - -if version.api < 13 then - clusters.WaterHeaterMode = require "WaterHeaterMode" -end - -local THERMOSTAT_MODE_MAP = { - [clusters.Thermostat.types.ThermostatSystemMode.OFF] = capabilities.thermostatMode.thermostatMode.off, - [clusters.Thermostat.types.ThermostatSystemMode.AUTO] = capabilities.thermostatMode.thermostatMode.auto, - [clusters.Thermostat.types.ThermostatSystemMode.COOL] = capabilities.thermostatMode.thermostatMode.cool, - [clusters.Thermostat.types.ThermostatSystemMode.HEAT] = capabilities.thermostatMode.thermostatMode.heat, - [clusters.Thermostat.types.ThermostatSystemMode.EMERGENCY_HEATING] = capabilities.thermostatMode.thermostatMode.emergency_heat, - [clusters.Thermostat.types.ThermostatSystemMode.PRECOOLING] = capabilities.thermostatMode.thermostatMode.precooling, - [clusters.Thermostat.types.ThermostatSystemMode.FAN_ONLY] = capabilities.thermostatMode.thermostatMode.fanonly, - [clusters.Thermostat.types.ThermostatSystemMode.DRY] = capabilities.thermostatMode.thermostatMode.dryair, - [clusters.Thermostat.types.ThermostatSystemMode.SLEEP] = capabilities.thermostatMode.thermostatMode.asleep, -} - -local THERMOSTAT_OPERATING_MODE_MAP = { - [0] = capabilities.thermostatOperatingState.thermostatOperatingState.heating, - [1] = capabilities.thermostatOperatingState.thermostatOperatingState.cooling, - [2] = capabilities.thermostatOperatingState.thermostatOperatingState.fan_only, - [3] = capabilities.thermostatOperatingState.thermostatOperatingState.heating, - [4] = capabilities.thermostatOperatingState.thermostatOperatingState.cooling, - [5] = capabilities.thermostatOperatingState.thermostatOperatingState.fan_only, - [6] = capabilities.thermostatOperatingState.thermostatOperatingState.fan_only, -} - -local WIND_MODE_MAP = { - [0] = capabilities.windMode.windMode.sleepWind, - [1] = capabilities.windMode.windMode.naturalWind -} - -local ROCK_MODE_MAP = { - [0] = capabilities.fanOscillationMode.fanOscillationMode.horizontal, - [1] = capabilities.fanOscillationMode.fanOscillationMode.vertical, - [2] = capabilities.fanOscillationMode.fanOscillationMode.swing -} - -local RAC_DEVICE_TYPE_ID = 0x0072 -local AP_DEVICE_TYPE_ID = 0x002D -local FAN_DEVICE_TYPE_ID = 0x002B -local WATER_HEATER_DEVICE_TYPE_ID = 0x050F -local HEAT_PUMP_DEVICE_TYPE_ID = 0x0309 -local THERMOSTAT_DEVICE_TYPE_ID = 0x0301 -local ELECTRICAL_SENSOR_DEVICE_TYPE_ID = 0x0510 - -local MIN_ALLOWED_PERCENT_VALUE = 0 -local MAX_ALLOWED_PERCENT_VALUE = 100 - -local CUMULATIVE_REPORTS_NOT_SUPPORTED = "__cumulative_reports_not_supported" -local LAST_IMPORTED_REPORT_TIMESTAMP = "__last_imported_report_timestamp" -local MINIMUM_ST_ENERGY_REPORT_INTERVAL = (15 * 60) -- 15 minutes, reported in seconds - -local TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP = "__total_cumulative_energy_imported_map" -local SUPPORTED_WATER_HEATER_MODES_WITH_IDX = "__supported_water_heater_modes_with_idx" -local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map" -local MGM3_PPM_CONVERSION_FACTOR = 24.45 - --- For RPC version >= 6, we can always assume that the values received from temperatureSetpoint --- are in Celsius, but we still limit the setpoint range to somewhat reasonable values. --- For RPC <= 5, this is a work around to handle when units for temperatureSetpoint is changed for the App. --- When units are switched, we will never know the units of the received command value as the arguments don't contain the unit. --- So to handle this we assume the following ranges considering usual thermostat/water-heater temperatures: --- Thermostat: --- 1. if the received setpoint command value is in range 5 ~ 40, it is inferred as *C --- 2. if the received setpoint command value is in range 41 ~ 104, it is inferred as *F -local THERMOSTAT_MAX_TEMP_IN_C = version.rpc >= 6 and 100.0 or 40.0 -local THERMOSTAT_MIN_TEMP_IN_C = version.rpc >= 6 and 0.0 or 5.0 --- Water Heater: --- 1. if the received setpoint command value is in range 30 ~ 80, it is inferred as *C --- 2. if the received setpoint command value is in range 86 ~ 176, it is inferred as *F -local WATER_HEATER_MAX_TEMP_IN_C = version.rpc >= 6 and 100.0 or 80.0 -local WATER_HEATER_MIN_TEMP_IN_C = version.rpc >= 6 and 0.0 or 30.0 - -local setpoint_limit_device_field = { - MIN_SETPOINT_DEADBAND_CHECKED = "MIN_SETPOINT_DEADBAND_CHECKED", - MIN_HEAT = "MIN_HEAT", - MAX_HEAT = "MAX_HEAT", - MIN_COOL = "MIN_COOL", - MAX_COOL = "MAX_COOL", - MIN_DEADBAND = "MIN_DEADBAND", - MIN_TEMP = "MIN_TEMP", - MAX_TEMP = "MAX_TEMP" -} - -local battery_support = { - NO_BATTERY = "NO_BATTERY", - BATTERY_LEVEL = "BATTERY_LEVEL", - BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE" -} - -local profiling_data = { - BATTERY_SUPPORT = "__BATTERY_SUPPORT", - THERMOSTAT_RUNNING_STATE_SUPPORT = "__THERMOSTAT_RUNNING_STATE_SUPPORT" -} - -local subscribed_attributes = { - [capabilities.switch.ID] = { - clusters.OnOff.attributes.OnOff - }, - [capabilities.temperatureMeasurement.ID] = { - clusters.Thermostat.attributes.LocalTemperature, - clusters.TemperatureMeasurement.attributes.MeasuredValue, - clusters.TemperatureMeasurement.attributes.MinMeasuredValue, - clusters.TemperatureMeasurement.attributes.MaxMeasuredValue - }, - [capabilities.relativeHumidityMeasurement.ID] = { - clusters.RelativeHumidityMeasurement.attributes.MeasuredValue - }, - [capabilities.thermostatMode.ID] = { - clusters.Thermostat.attributes.SystemMode, - clusters.Thermostat.attributes.ControlSequenceOfOperation - }, - [capabilities.thermostatOperatingState.ID] = { - clusters.Thermostat.attributes.ThermostatRunningState - }, - [capabilities.thermostatFanMode.ID] = { - clusters.FanControl.attributes.FanModeSequence, - clusters.FanControl.attributes.FanMode - }, - [capabilities.thermostatCoolingSetpoint.ID] = { - clusters.Thermostat.attributes.OccupiedCoolingSetpoint, - clusters.Thermostat.attributes.AbsMinCoolSetpointLimit, - clusters.Thermostat.attributes.AbsMaxCoolSetpointLimit - }, - [capabilities.thermostatHeatingSetpoint.ID] = { - clusters.Thermostat.attributes.OccupiedHeatingSetpoint, - clusters.Thermostat.attributes.AbsMinHeatSetpointLimit, - clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit - }, - [capabilities.airConditionerFanMode.ID] = { - clusters.FanControl.attributes.FanModeSequence, - clusters.FanControl.attributes.FanMode - }, - [capabilities.airPurifierFanMode.ID] = { - clusters.FanControl.attributes.FanModeSequence, - clusters.FanControl.attributes.FanMode - }, - [capabilities.fanMode.ID] = { - clusters.FanControl.attributes.FanModeSequence, - clusters.FanControl.attributes.FanMode - }, - [capabilities.fanSpeedPercent.ID] = { - clusters.FanControl.attributes.PercentCurrent - }, - [capabilities.windMode.ID] = { - clusters.FanControl.attributes.WindSupport, - clusters.FanControl.attributes.WindSetting - }, - [capabilities.fanOscillationMode.ID] = { - clusters.FanControl.attributes.RockSupport, - clusters.FanControl.attributes.RockSetting - }, - [capabilities.battery.ID] = { - clusters.PowerSource.attributes.BatPercentRemaining - }, - [capabilities.batteryLevel.ID] = { - clusters.PowerSource.attributes.BatChargeLevel - }, - [capabilities.filterState.ID] = { - clusters.HepaFilterMonitoring.attributes.Condition, - clusters.ActivatedCarbonFilterMonitoring.attributes.Condition - }, - [capabilities.filterStatus.ID] = { - clusters.HepaFilterMonitoring.attributes.ChangeIndication, - clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication - }, - [capabilities.airQualityHealthConcern.ID] = { - clusters.AirQuality.attributes.AirQuality - }, - [capabilities.carbonMonoxideMeasurement.ID] = { - clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue, - clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.carbonMonoxideHealthConcern.ID] = { - clusters.CarbonMonoxideConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.carbonDioxideMeasurement.ID] = { - clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasuredValue, - clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.carbonDioxideHealthConcern.ID] = { - clusters.CarbonDioxideConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.nitrogenDioxideMeasurement.ID] = { - clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasuredValue, - clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasurementUnit - }, - [capabilities.nitrogenDioxideHealthConcern.ID] = { - clusters.NitrogenDioxideConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.ozoneMeasurement.ID] = { - clusters.OzoneConcentrationMeasurement.attributes.MeasuredValue, - clusters.OzoneConcentrationMeasurement.attributes.MeasurementUnit - }, - [capabilities.ozoneHealthConcern.ID] = { - clusters.OzoneConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.formaldehydeMeasurement.ID] = { - clusters.FormaldehydeConcentrationMeasurement.attributes.MeasuredValue, - clusters.FormaldehydeConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.formaldehydeHealthConcern.ID] = { - clusters.FormaldehydeConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.veryFineDustSensor.ID] = { - clusters.Pm1ConcentrationMeasurement.attributes.MeasuredValue, - clusters.Pm1ConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.veryFineDustHealthConcern.ID] = { - clusters.Pm1ConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.fineDustHealthConcern.ID] = { - clusters.Pm25ConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.fineDustSensor.ID] = { - clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue, - clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.dustSensor.ID] = { - clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue, - clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit, - clusters.Pm10ConcentrationMeasurement.attributes.MeasuredValue, - clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.dustHealthConcern.ID] = { - clusters.Pm10ConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.radonMeasurement.ID] = { - clusters.RadonConcentrationMeasurement.attributes.MeasuredValue, - clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.radonHealthConcern.ID] = { - clusters.RadonConcentrationMeasurement.attributes.LevelValue, - }, - [capabilities.tvocMeasurement.ID] = { - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasuredValue, - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.tvocHealthConcern.ID] = { - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue - }, - [capabilities.powerMeter.ID] = { - clusters.ElectricalPowerMeasurement.attributes.ActivePower - }, - [capabilities.mode.ID] = { - clusters.WaterHeaterMode.attributes.CurrentMode, - clusters.WaterHeaterMode.attributes.SupportedModes - }, - [capabilities.powerConsumptionReport.ID] = { - clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, - clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported - }, - [capabilities.energyMeter.ID] = { - clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, - clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported - }, -} - -local function supports_capability_by_id_modular(device, capability, component) - if not device:get_field(SUPPORTED_COMPONENT_CAPABILITIES) then - device.log.warn_with({hub_logs = true}, "Device has overriden supports_capability_by_id, but does not have supported capabilities set.") - return false - end - for _, component_capabilities in ipairs(device:get_field(SUPPORTED_COMPONENT_CAPABILITIES)) do - local comp_id = component_capabilities[1] - local capability_ids = component_capabilities[2] - if (component == nil) or (component == comp_id) then - for _, cap in ipairs(capability_ids) do - if cap == capability then - return true - end - end - end - end - return false -end - -local function epoch_to_iso8601(time) - return os.date("!%Y-%m-%dT%H:%M:%SZ", time) -end - -local get_total_cumulative_energy_imported = function(device) - local total_cumulative_energy_imported = device:get_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP) or {} - local total_energy = 0 - for _, energyWh in pairs(total_cumulative_energy_imported) do - total_energy = total_energy + energyWh - end - return total_energy -end - -local function report_power_consumption_to_st_energy(device, latest_total_imported_energy_wh) - local current_time = os.time() - local last_time = device:get_field(LAST_IMPORTED_REPORT_TIMESTAMP) or 0 - - -- Ensure that the previous report was sent at least 15 minutes ago - if MINIMUM_ST_ENERGY_REPORT_INTERVAL >= (current_time - last_time) then - return - end - - device:set_field(LAST_IMPORTED_REPORT_TIMESTAMP, current_time, { persist = true }) - - -- Calculate the energy delta between reports - local energy_delta_wh = 0.0 - local previous_imported_report = device:get_latest_state("main", capabilities.powerConsumptionReport.ID, - capabilities.powerConsumptionReport.powerConsumption.NAME) - if previous_imported_report and previous_imported_report.energy then - energy_delta_wh = math.max(latest_total_imported_energy_wh - previous_imported_report.energy, 0.0) - end - - -- Report the energy consumed during the time interval. The unit of these values should be 'Wh' - device:emit_component_event(device.profile.components["main"], capabilities.powerConsumptionReport.powerConsumption({ - start = epoch_to_iso8601(last_time), - ["end"] = epoch_to_iso8601(current_time - 1), - deltaEnergy = energy_delta_wh, - energy = latest_total_imported_energy_wh - })) -end - -local function device_removed(driver, device) - device.log.info("device removed") -end - -local function tbl_contains(array, value) - for idx, element in ipairs(array) do - if element == value then - return true, idx - end - end - return false, nil -end - -local function get_field_for_endpoint(device, field, endpoint) - return device:get_field(string.format("%s_%d", field, endpoint)) -end - -local function set_field_for_endpoint(device, field, endpoint, value, additional_params) - device:set_field(string.format("%s_%d", field, endpoint), value, additional_params) -end - -local function find_default_endpoint(device, cluster) - local res = device.MATTER_DEFAULT_ENDPOINT - local eps = embedded_cluster_utils.get_endpoints(device, cluster) - table.sort(eps) - for _, v in ipairs(eps) do - if v ~= 0 then --0 is the matter RootNode endpoint - return v - end - end - device.log.warn(string.format("Did not find default endpoint, will use endpoint %d instead", device.MATTER_DEFAULT_ENDPOINT)) - return res -end - -local function component_to_endpoint(device, component_name, cluster_id) - -- Use the find_default_endpoint function to return the first endpoint that - -- supports a given cluster. - local component_to_endpoint_map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) - if component_to_endpoint_map ~= nil and component_to_endpoint_map[component_name] ~= nil then - return component_to_endpoint_map[component_name] - end - if not cluster_id then return device.MATTER_DEFAULT_ENDPOINT end - return find_default_endpoint(device, cluster_id) -end - -local endpoint_to_component = function (device, endpoint_id) - local component_to_endpoint_map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) - if component_to_endpoint_map ~= nil then - for comp, ep in pairs(component_to_endpoint_map) do - if ep == endpoint_id then - return comp - end - end - end - return "main" -end - -local function device_init(driver, device) - if device:get_field(SUPPORTED_COMPONENT_CAPABILITIES) and (version.api < 15 or version.rpc < 9) then - -- assume that device is using a modular profile on 0.57 FW, override supports_capability_by_id - -- library function to utilize optional capabilities - device:extend_device("supports_capability_by_id", supports_capability_by_id_modular) - end - device:subscribe() - device:set_component_to_endpoint_fn(component_to_endpoint) - device:set_endpoint_to_component_fn(endpoint_to_component) - if not device:get_field(setpoint_limit_device_field.MIN_SETPOINT_DEADBAND_CHECKED) then - local auto_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.AUTOMODE}) - --Query min setpoint deadband if needed - if #auto_eps ~= 0 and device:get_field(setpoint_limit_device_field.MIN_DEADBAND) == nil then - local deadband_read = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) - deadband_read:merge(clusters.Thermostat.attributes.MinSetpointDeadBand:read()) - device:send(deadband_read) - end - end - - -- device energy reporting must be handled cumulatively, periodically, or by both simulatanously. - -- To ensure a single source of truth, we only handle a device's periodic reporting if cumulative reporting is not supported. - local electrical_energy_measurement_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID) - if #electrical_energy_measurement_eps > 0 then - local cumulative_energy_eps = embedded_cluster_utils.get_endpoints( - device, - clusters.ElectricalEnergyMeasurement.ID, - {feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY} - ) - if #cumulative_energy_eps == 0 then device:set_field(CUMULATIVE_REPORTS_NOT_SUPPORTED, true, {persist = false}) end - end -end - -local function info_changed(driver, device, event, args) - if device:get_field(SUPPORTED_COMPONENT_CAPABILITIES) then - -- This indicates the device should be using a modular profile, so - -- re-up subscription with new capabilities using the modular supports_capability override - device:extend_device("supports_capability_by_id", supports_capability_by_id_modular) - end - - if device.profile.id ~= args.old_st_store.profile.id then - device:subscribe() - end -end - -local function get_endpoints_for_dt(device, device_type) - local endpoints = {} - for _, ep in ipairs(device.endpoints) do - for _, dt in ipairs(ep.device_types) do - if dt.device_type_id == device_type then - table.insert(endpoints, ep.endpoint_id) - break - end - end - end - table.sort(endpoints) - return endpoints -end - -local function get_device_type(device) - -- For cases where a device has multiple device types, this list indicates which - -- device type will be the "main" device type for purposes of selecting a profile - -- with an appropriate category. This is done to promote consistency between - -- devices with similar device type compositions that may report their device types - -- listed in different orders - local device_type_priority = { - [HEAT_PUMP_DEVICE_TYPE_ID] = 1, - [RAC_DEVICE_TYPE_ID] = 2, - [AP_DEVICE_TYPE_ID] = 3, - [THERMOSTAT_DEVICE_TYPE_ID] = 4, - [FAN_DEVICE_TYPE_ID] = 5, - [WATER_HEATER_DEVICE_TYPE_ID] = 6, - } - - local main_device_type = false - - for _, ep in ipairs(device.endpoints) do - if ep.device_types ~= nil then - for _, dt in ipairs(ep.device_types) do - if not device_type_priority[main_device_type] or (device_type_priority[dt.device_type_id] and - device_type_priority[dt.device_type_id] < device_type_priority[main_device_type]) then - main_device_type = dt.device_type_id - end - end - end - end - - return main_device_type -end - -local AIR_QUALITY_MAP = { - {capabilities.carbonDioxideMeasurement.ID, "-co2", clusters.CarbonDioxideConcentrationMeasurement}, - {capabilities.carbonDioxideHealthConcern.ID, "-co2", clusters.CarbonDioxideConcentrationMeasurement}, - {capabilities.carbonMonoxideMeasurement.ID, "-co", clusters.CarbonMonoxideConcentrationMeasurement}, - {capabilities.carbonMonoxideHealthConcern.ID, "-co", clusters.CarbonMonoxideConcentrationMeasurement}, - {capabilities.dustSensor.ID, "-pm10", clusters.Pm10ConcentrationMeasurement}, - {capabilities.dustHealthConcern.ID, "-pm10", clusters.Pm10ConcentrationMeasurement}, - {capabilities.fineDustSensor.ID, "-pm25", clusters.Pm25ConcentrationMeasurement}, - {capabilities.fineDustHealthConcern.ID, "-pm25", clusters.Pm25ConcentrationMeasurement}, - {capabilities.formaldehydeMeasurement.ID, "-ch2o", clusters.FormaldehydeConcentrationMeasurement}, - {capabilities.formaldehydeHealthConcern.ID, "-ch2o", clusters.FormaldehydeConcentrationMeasurement}, - {capabilities.nitrogenDioxideHealthConcern.ID, "-no2", clusters.NitrogenDioxideConcentrationMeasurement}, - {capabilities.nitrogenDioxideMeasurement.ID, "-no2", clusters.NitrogenDioxideConcentrationMeasurement}, - {capabilities.ozoneHealthConcern.ID, "-ozone", clusters.OzoneConcentrationMeasurement}, - {capabilities.ozoneMeasurement.ID, "-ozone", clusters.OzoneConcentrationMeasurement}, - {capabilities.radonHealthConcern.ID, "-radon", clusters.RadonConcentrationMeasurement}, - {capabilities.radonMeasurement.ID, "-radon", clusters.RadonConcentrationMeasurement}, - {capabilities.tvocHealthConcern.ID, "-tvoc", clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement}, - {capabilities.tvocMeasurement.ID, "-tvoc", clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement}, - {capabilities.veryFineDustHealthConcern.ID, "-pm1", clusters.Pm1ConcentrationMeasurement}, - {capabilities.veryFineDustSensor.ID, "-pm1", clusters.Pm1ConcentrationMeasurement}, -} - -local function create_level_measurement_profile(device) - local meas_name, level_name = "", "" - for _, details in ipairs(AIR_QUALITY_MAP) do - local cap_id = details[1] - local cluster = details[3] - -- capability describes either a HealthConcern or Measurement/Sensor - if (cap_id:match("HealthConcern$")) then - local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) - if #attr_eps > 0 then - level_name = level_name .. details[2] - end - elseif (cap_id:match("Measurement$") or cap_id:match("Sensor$")) then - local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.NUMERIC_MEASUREMENT }) - if #attr_eps > 0 then - meas_name = meas_name .. details[2] - end - end - end - return meas_name, level_name -end - -local function supported_level_measurements(device) - local measurement_caps, level_caps = {}, {} - for _, details in ipairs(AIR_QUALITY_MAP) do - local cap_id = details[1] - local cluster = details[3] - -- capability describes either a HealthConcern or Measurement/Sensor - if (cap_id:match("HealthConcern$")) then - local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) - if #attr_eps > 0 then - table.insert(level_caps, cap_id) - end - elseif (cap_id:match("Measurement$") or cap_id:match("Sensor$")) then - local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.NUMERIC_MEASUREMENT }) - if #attr_eps > 0 then - table.insert(measurement_caps, cap_id) - end - end - end - return measurement_caps, level_caps -end - -local function create_air_quality_sensor_profile(device) - local aqs_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) - local profile_name = "" - if #aqs_eps > 0 then - profile_name = profile_name .. "-aqs" - end - local meas_name, level_name = create_level_measurement_profile(device) - if meas_name ~= "" then - profile_name = profile_name .. meas_name .. "-meas" - end - if level_name ~= "" then - profile_name = profile_name .. level_name .. "-level" - end - return profile_name -end - -local function create_fan_profile(device) - local fan_eps = device:get_endpoints(clusters.FanControl.ID) - local wind_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.FanControlFeature.WIND}) - local rock_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.Feature.ROCKING}) - local profile_name = "" - if #fan_eps > 0 then - profile_name = profile_name .. "-fan" - end - if #rock_eps > 0 then - profile_name = profile_name .. "-rock" - end - if #wind_eps > 0 then - profile_name = profile_name .. "-wind" - end - return profile_name -end - -local function create_air_purifier_profile(device) - local hepa_filter_eps = embedded_cluster_utils.get_endpoints(device, clusters.HepaFilterMonitoring.ID) - local ac_filter_eps = embedded_cluster_utils.get_endpoints(device, clusters.ActivatedCarbonFilterMonitoring.ID) - local fan_eps_seen = false - local profile_name = "air-purifier" - if #hepa_filter_eps > 0 then - profile_name = profile_name .. "-hepa" - end - if #ac_filter_eps > 0 then - profile_name = profile_name .. "-ac" - end - - -- air purifier profiles include -fan later in the name for historical reasons. - -- save this information for use at that point. - local fan_profile = create_fan_profile(device) - if fan_profile ~= "" then - fan_eps_seen = true - end - fan_profile = string.gsub(fan_profile, "-fan", "") - profile_name = profile_name .. fan_profile - - return profile_name, fan_eps_seen -end - -local function create_thermostat_modes_profile(device) - local heat_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.HEATING}) - local cool_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.COOLING}) - - local thermostat_modes = "" - if #heat_eps == 0 and #cool_eps == 0 then - return "No Heating nor Cooling Support" - elseif #heat_eps > 0 and #cool_eps == 0 then - thermostat_modes = thermostat_modes .. "-heating-only" - elseif #cool_eps > 0 and #heat_eps == 0 then - thermostat_modes = thermostat_modes .. "-cooling-only" - end - return thermostat_modes -end - -local function profiling_data_still_required(device) - for _, field in pairs(profiling_data) do - if device:get_field(field) == nil then - return true -- data still required if a field is nil - end - end - return false -end - -local function match_profile_switch(driver, device) - if profiling_data_still_required(device) then return end - - local running_state_supported = device:get_field(profiling_data.THERMOSTAT_RUNNING_STATE_SUPPORT) - local battery_supported = device:get_field(profiling_data.BATTERY_SUPPORT) - - local thermostat_eps = device:get_endpoints(clusters.Thermostat.ID) - local humidity_eps = device:get_endpoints(clusters.RelativeHumidityMeasurement.ID) - local device_type = get_device_type(device) - local profile_name - if device_type == RAC_DEVICE_TYPE_ID then - profile_name = "room-air-conditioner" - - if #humidity_eps > 0 then - profile_name = profile_name .. "-humidity" - end - - -- Room AC does not support the rocking feature of FanControl. - local fan_name = create_fan_profile(device) - fan_name = string.gsub(fan_name, "-rock", "") - profile_name = profile_name .. fan_name - - local thermostat_modes = create_thermostat_modes_profile(device) - if thermostat_modes == "" then - profile_name = profile_name .. "-heating-cooling" - else - device.log.warn_with({hub_logs=true}, "Device does not support both heating and cooling. No matching profile") - return - end - - if profile_name == "room-air-conditioner-humidity-fan-wind-heating-cooling" then - profile_name = "room-air-conditioner" - end - - if not running_state_supported and profile_name == "room-air-conditioner-fan-heating-cooling" then - profile_name = profile_name .. "-nostate" - end - - elseif device_type == FAN_DEVICE_TYPE_ID then - profile_name = create_fan_profile(device) - -- remove leading "-" - profile_name = string.sub(profile_name, 2) - if profile_name == "fan" then - profile_name = "fan-generic" - end - - elseif device_type == AP_DEVICE_TYPE_ID then - local fan_eps_found - profile_name, fan_eps_found = create_air_purifier_profile(device) - if #thermostat_eps > 0 then - profile_name = profile_name .. "-thermostat" - - if #humidity_eps > 0 then - profile_name = profile_name .. "-humidity" - end - - if fan_eps_found then - profile_name = profile_name .. "-fan" - end - - local thermostat_modes = create_thermostat_modes_profile(device) - if thermostat_modes ~= "No Heating nor Cooling Support" then - profile_name = profile_name .. thermostat_modes - end - - if not running_state_supported then - profile_name = profile_name .. "-nostate" - end - - if battery_supported == battery_support.BATTERY_LEVEL then - profile_name = profile_name .. "-batteryLevel" - elseif battery_supported == battery_support.NO_BATTERY then - profile_name = profile_name .. "-nobattery" - end - elseif #device:get_endpoints(clusters.TemperatureMeasurement.ID) > 0 then - profile_name = profile_name .. "-temperature" - - if #humidity_eps > 0 then - profile_name = profile_name .. "-humidity" - end - - if fan_eps_found then - profile_name = profile_name .. "-fan" - end - end - profile_name = profile_name .. create_air_quality_sensor_profile(device) - elseif device_type == WATER_HEATER_DEVICE_TYPE_ID then - -- If a Water Heater is composed of Electrical Sensor device type, it must support both ElectricalEnergyMeasurement and - -- ElectricalPowerMeasurement clusters. - local electrical_sensor_eps = get_endpoints_for_dt(device, ELECTRICAL_SENSOR_DEVICE_TYPE_ID) or {} - if #electrical_sensor_eps > 0 then - profile_name = "water-heater-power-energy-powerConsumption" - end - elseif device_type == HEAT_PUMP_DEVICE_TYPE_ID then - profile_name = "heat-pump" - local MAX_HEAT_PUMP_THERMOSTAT_COMPONENTS = 2 - for i = 1, math.min(MAX_HEAT_PUMP_THERMOSTAT_COMPONENTS, #thermostat_eps) do - profile_name = profile_name .. "-thermostat" - if tbl_contains(humidity_eps, thermostat_eps[i]) then - profile_name = profile_name .. "-humidity" - end - end - elseif #thermostat_eps > 0 then - profile_name = "thermostat" - - if #humidity_eps > 0 then - profile_name = profile_name .. "-humidity" - end - - -- thermostat profiles support neither wind nor rocking FanControl attributes - local fan_name = create_fan_profile(device) - if fan_name ~= "" then - profile_name = profile_name .. "-fan" - end - - local thermostat_modes = create_thermostat_modes_profile(device) - if thermostat_modes == "No Heating nor Cooling Support" then - device.log.warn_with({hub_logs=true}, "Device does not support either heating or cooling. No matching profile") - return - else - profile_name = profile_name .. thermostat_modes - end - - if not running_state_supported then - profile_name = profile_name .. "-nostate" - end - - if battery_supported == battery_support.BATTERY_LEVEL then - profile_name = profile_name .. "-batteryLevel" - elseif battery_supported == battery_support.NO_BATTERY then - profile_name = profile_name .. "-nobattery" - end - else - device.log.warn_with({hub_logs=true}, "Device type is not supported in thermostat driver") - return - end - - if profile_name then - device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) - device:try_update_metadata({profile = profile_name}) - end - -- clear all profiling data fields after profiling is complete. - for _, field in pairs(profiling_data) do - device:set_field(field, nil) - end -end - -local function get_thermostat_optional_capabilities(device) - local heat_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.HEATING}) - local cool_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.COOLING}) - local running_state_supported = device:get_field(profiling_data.THERMOSTAT_RUNNING_STATE_SUPPORT) - - local supported_thermostat_capabilities = {} - - if #heat_eps > 0 then - table.insert(supported_thermostat_capabilities, capabilities.thermostatHeatingSetpoint.ID) - end - if #cool_eps > 0 then - table.insert(supported_thermostat_capabilities, capabilities.thermostatCoolingSetpoint.ID) - end - - if running_state_supported then - table.insert(supported_thermostat_capabilities, capabilities.thermostatOperatingState.ID) - end - - return supported_thermostat_capabilities -end - -local function get_air_quality_optional_capabilities(device) - local supported_air_quality_capabilities = {} - - local measurement_caps, level_caps = supported_level_measurements(device) - - for _, cap_id in ipairs(measurement_caps) do - table.insert(supported_air_quality_capabilities, cap_id) - end - - for _, cap_id in ipairs(level_caps) do - table.insert(supported_air_quality_capabilities, cap_id) - end - - return supported_air_quality_capabilities -end - -local function match_modular_profile_air_purifer(driver, device) - local optional_supported_component_capabilities = {} - local main_component_capabilities = {} - local hepa_filter_component_capabilities = {} - local ac_filter_component_capabilties = {} - local profile_name = "air-purifier-modular" - - local MAIN_COMPONENT_IDX = 1 - local CAPABILITIES_LIST_IDX = 2 - - local humidity_eps = device:get_endpoints(clusters.RelativeHumidityMeasurement.ID) - local temp_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureMeasurement.ID) - if #humidity_eps > 0 then - table.insert(main_component_capabilities, capabilities.relativeHumidityMeasurement.ID) - end - if #temp_eps > 0 then - table.insert(main_component_capabilities, capabilities.temperatureMeasurement.ID) - end - - local hepa_filter_eps = embedded_cluster_utils.get_endpoints(device, clusters.HepaFilterMonitoring.ID) - local ac_filter_eps = embedded_cluster_utils.get_endpoints(device, clusters.ActivatedCarbonFilterMonitoring.ID) - - if #hepa_filter_eps > 0 then - local filter_state_eps = embedded_cluster_utils.get_endpoints(device, clusters.HepaFilterMonitoring.ID, {feature_bitmap = clusters.HepaFilterMonitoring.types.Feature.CONDITION}) - if #filter_state_eps > 0 then - table.insert(hepa_filter_component_capabilities, capabilities.filterState.ID) - end - - table.insert(hepa_filter_component_capabilities, capabilities.filterStatus.ID) - end - if #ac_filter_eps > 0 then - local filter_state_eps = embedded_cluster_utils.get_endpoints(device, clusters.ActivatedCarbonFilterMonitoring.ID, {feature_bitmap = clusters.ActivatedCarbonFilterMonitoring.types.Feature.CONDITION}) - if #filter_state_eps > 0 then - table.insert(ac_filter_component_capabilties, capabilities.filterState.ID) - end - - table.insert(ac_filter_component_capabilties, capabilities.filterStatus.ID) - end - - -- determine fan capabilities, note that airPurifierFanMode is already mandatory - local rock_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.Feature.ROCKING}) - local wind_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.FanControlFeature.WIND}) - - if #rock_eps > 0 then - table.insert(main_component_capabilities, capabilities.fanOscillationMode.ID) - end - if #wind_eps > 0 then - table.insert(main_component_capabilities, capabilities.windMode.ID) - end - - local thermostat_eps = device:get_endpoints(clusters.Thermostat.ID) - - if #thermostat_eps > 0 then - -- thermostatMode and temperatureMeasurement should be expected if thermostat is present - table.insert(main_component_capabilities, capabilities.thermostatMode.ID) - - -- only add temperatureMeasurement if it is not already added via TemperatureMeasurement cluster support - if #temp_eps == 0 then - table.insert(main_component_capabilities, capabilities.temperatureMeasurement.ID) - end - local thermostat_capabilities = get_thermostat_optional_capabilities(device) - for _, capability_id in pairs(thermostat_capabilities) do - table.insert(main_component_capabilities, capability_id) - end - end - - local aqs_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) - if #aqs_eps > 0 then - table.insert(main_component_capabilities, capabilities.airQualityHealthConcern.ID) - end - - local supported_air_quality_capabilities = get_air_quality_optional_capabilities(device) - for _, capability_id in pairs(supported_air_quality_capabilities) do - table.insert(main_component_capabilities, capability_id) - end - - table.insert(optional_supported_component_capabilities, {"main", main_component_capabilities}) - if #ac_filter_component_capabilties > 0 then - table.insert(optional_supported_component_capabilities, {"activatedCarbonFilter", ac_filter_component_capabilties}) - end - if #hepa_filter_component_capabilities > 0 then - table.insert(optional_supported_component_capabilities, {"hepaFilter", hepa_filter_component_capabilities}) - end - - device:try_update_metadata({profile = profile_name, optional_component_capabilities = optional_supported_component_capabilities}) - - -- earlier modular profile gating (min api v14, rpc 8) ensures we are running >= 0.57 FW. - -- This gating specifies a workaround required only for 0.57 FW, which is not needed for 0.58 and higher. - if version.api < 15 or version.rpc < 9 then - -- add mandatory capabilities for subscription - local total_supported_capabilities = optional_supported_component_capabilities - table.insert(total_supported_capabilities[MAIN_COMPONENT_IDX][CAPABILITIES_LIST_IDX], capabilities.airPurifierFanMode.ID) - table.insert(total_supported_capabilities[MAIN_COMPONENT_IDX][CAPABILITIES_LIST_IDX], capabilities.fanSpeedPercent.ID) - table.insert(total_supported_capabilities[MAIN_COMPONENT_IDX][CAPABILITIES_LIST_IDX], capabilities.refresh.ID) - table.insert(total_supported_capabilities[MAIN_COMPONENT_IDX][CAPABILITIES_LIST_IDX], capabilities.firmwareUpdate.ID) - - device:set_field(SUPPORTED_COMPONENT_CAPABILITIES, total_supported_capabilities, { persist = true }) - end -end - -local function match_modular_profile_thermostat(driver, device) - local optional_supported_component_capabilities = {} - local main_component_capabilities = {} - local profile_name = "thermostat-modular" - - local humidity_eps = device:get_endpoints(clusters.RelativeHumidityMeasurement.ID) - if #humidity_eps > 0 then - table.insert(main_component_capabilities, capabilities.relativeHumidityMeasurement.ID) - end - - -- determine fan capabilities - local fan_eps = device:get_endpoints(clusters.FanControl.ID) - local rock_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.Feature.ROCKING}) - local wind_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.FanControlFeature.WIND}) - - if #fan_eps > 0 then - table.insert(main_component_capabilities, capabilities.fanMode.ID) - end - if #rock_eps > 0 then - table.insert(main_component_capabilities, capabilities.fanOscillationMode.ID) - end - if #wind_eps > 0 then - table.insert(main_component_capabilities, capabilities.windMode.ID) - end - - local thermostat_capabilities = get_thermostat_optional_capabilities(device) - for _, capability_id in pairs(thermostat_capabilities) do - table.insert(main_component_capabilities, capability_id) - end - - local battery_supported = device:get_field(profiling_data.BATTERY_SUPPORT) - if battery_supported == battery_support.BATTERY_LEVEL then - table.insert(main_component_capabilities, capabilities.batteryLevel.ID) - elseif battery_supported == battery_support.BATTERY_PERCENTAGE then - table.insert(main_component_capabilities, capabilities.battery.ID) - end - - table.insert(optional_supported_component_capabilities, {"main", main_component_capabilities}) - device:try_update_metadata({profile = profile_name, optional_component_capabilities = optional_supported_component_capabilities}) - - -- earlier modular profile gating (min api v14, rpc 8) ensures we are running >= 0.57 FW. - -- This gating specifies a workaround required only for 0.57 FW, which is not needed for 0.58 and higher. - if version.api < 15 or version.rpc < 9 then - -- add mandatory capabilities for subscription - local total_supported_capabilities = optional_supported_component_capabilities - table.insert(main_component_capabilities, capabilities.thermostatMode.ID) - table.insert(main_component_capabilities, capabilities.temperatureMeasurement.ID) - table.insert(main_component_capabilities, capabilities.refresh.ID) - table.insert(main_component_capabilities, capabilities.firmwareUpdate.ID) - - device:set_field(SUPPORTED_COMPONENT_CAPABILITIES, total_supported_capabilities, { persist = true }) - end -end - -local function match_modular_profile_room_ac(driver, device) - local running_state_supported = device:get_field(profiling_data.THERMOSTAT_RUNNING_STATE_SUPPORT) - local humidity_eps = device:get_endpoints(clusters.RelativeHumidityMeasurement.ID) - local optional_supported_component_capabilities = {} - local main_component_capabilities = {} - local profile_name = "room-air-conditioner-modular" - - if #humidity_eps > 0 then - table.insert(main_component_capabilities, capabilities.relativeHumidityMeasurement.ID) - end - - -- determine fan capabilities - local fan_eps = device:get_endpoints(clusters.FanControl.ID) - local wind_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.FanControlFeature.WIND}) - -- Note: Room AC does not support the rocking feature of FanControl. - - if #fan_eps > 0 then - table.insert(main_component_capabilities, capabilities.airConditionerFanMode.ID) - table.insert(main_component_capabilities, capabilities.fanSpeedPercent.ID) - end - if #wind_eps > 0 then - table.insert(main_component_capabilities, capabilities.windMode.ID) - end - - local heat_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.HEATING}) - local cool_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.COOLING}) - - if #heat_eps > 0 then - table.insert(main_component_capabilities, capabilities.thermostatHeatingSetpoint.ID) - end - if #cool_eps > 0 then - table.insert(main_component_capabilities, capabilities.thermostatCoolingSetpoint.ID) - end - - if running_state_supported then - table.insert(main_component_capabilities, capabilities.thermostatOperatingState.ID) - end - - table.insert(optional_supported_component_capabilities, {"main", main_component_capabilities}) - device:try_update_metadata({profile = profile_name, optional_component_capabilities = optional_supported_component_capabilities}) - - -- earlier modular profile gating (min api v14, rpc 8) ensures we are running >= 0.57 FW. - -- This gating specifies a workaround required only for 0.57 FW, which is not needed for 0.58 and higher. - if version.api < 15 or version.rpc < 9 then - -- add mandatory capabilities for subscription - local total_supported_capabilities = optional_supported_component_capabilities - table.insert(main_component_capabilities, capabilities.switch.ID) - table.insert(main_component_capabilities, capabilities.temperatureMeasurement.ID) - table.insert(main_component_capabilities, capabilities.thermostatMode.ID) - table.insert(main_component_capabilities, capabilities.refresh.ID) - table.insert(main_component_capabilities, capabilities.firmwareUpdate.ID) - - device:set_field(SUPPORTED_COMPONENT_CAPABILITIES, total_supported_capabilities, { persist = true }) - end -end - -local function match_modular_profile(driver, device, device_type) - if profiling_data_still_required(device) then return end - - if device_type == AP_DEVICE_TYPE_ID then - match_modular_profile_air_purifer(driver, device) - elseif device_type == RAC_DEVICE_TYPE_ID then - match_modular_profile_room_ac(driver, device) - elseif device_type == THERMOSTAT_DEVICE_TYPE_ID then - match_modular_profile_thermostat(driver, device) - else - device.log.warn_with({hub_logs=true}, "Device type is not supported by modular profile in thermostat driver, trying profile switch instead") - match_profile_switch(driver, device) - return - end - - -- clear all profiling data fields after profiling is complete. - for _, field in pairs(profiling_data) do - device:set_field(field, nil) - end -end - -local function supports_modular_profile(device) - local supported_modular_device_types = { - AP_DEVICE_TYPE_ID, - RAC_DEVICE_TYPE_ID, - THERMOSTAT_DEVICE_TYPE_ID, - } - local device_type = get_device_type(device) - if not tbl_contains(supported_modular_device_types, device_type) then - device_type = false - end - return version.api >= 14 and version.rpc >= 8 and device_type -end - -function match_profile(driver, device) - local modular_device_type = supports_modular_profile(device) - if modular_device_type then - match_modular_profile(driver, device, modular_device_type) - else - match_profile_switch(driver, device) - end -end - -local function do_configure(driver, device) - match_profile(driver, device) -end - -local function driver_switched(driver, device) - match_profile(driver, device) -end - -local function device_added(driver, device) - local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) - req:merge(clusters.Thermostat.attributes.ControlSequenceOfOperation:read(device)) - req:merge(clusters.FanControl.attributes.FanModeSequence:read(device)) - req:merge(clusters.FanControl.attributes.WindSupport:read(device)) - req:merge(clusters.FanControl.attributes.RockSupport:read(device)) - - local thermostat_eps = device:get_endpoints(clusters.Thermostat.ID) - if #thermostat_eps > 0 then - req:merge(clusters.Thermostat.attributes.AttributeList:read(device)) - else - device:set_field(profiling_data.THERMOSTAT_RUNNING_STATE_SUPPORT, false) - end - local battery_feature_eps = device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) - if #battery_feature_eps > 0 then - req:merge(clusters.PowerSource.attributes.AttributeList:read(device)) - else - device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY) - end - device:send(req) - local heat_pump_eps = get_endpoints_for_dt(device, HEAT_PUMP_DEVICE_TYPE_ID) or {} - if #heat_pump_eps > 0 then - local thermostat_eps = get_endpoints_for_dt(device, THERMOSTAT_DEVICE_TYPE_ID) or {} - local component_to_endpoint_map = { - ["thermostatOne"] = thermostat_eps[1], - ["thermostatTwo"] = thermostat_eps[2], - } - device:set_field(COMPONENT_TO_ENDPOINT_MAP, component_to_endpoint_map, {persist = true}) - end -end - -local function store_unit_factory(capability_name) - return function(driver, device, ib, response) - device:set_field(capability_name.."_unit", ib.data.value, {persist = true}) - end -end - -local units = { - PPM = 0, - PPB = 1, - PPT = 2, - MGM3 = 3, - UGM3 = 4, - NGM3 = 5, - PM3 = 6, - BQM3 = 7, - PCIL = 0xFF -- not in matter spec -} - -local unit_strings = { - [units.PPM] = "ppm", - [units.PPB] = "ppb", - [units.PPT] = "ppt", - [units.MGM3] = "mg/m^3", - [units.NGM3] = "ng/m^3", - [units.UGM3] = "μg/m^3", - [units.BQM3] = "Bq/m^3", - [units.PCIL] = "pCi/L" -} - -local unit_default = { - [capabilities.carbonMonoxideMeasurement.NAME] = units.PPM, - [capabilities.carbonDioxideMeasurement.NAME] = units.PPM, - [capabilities.nitrogenDioxideMeasurement.NAME] = units.PPM, - [capabilities.ozoneMeasurement.NAME] = units.PPM, - [capabilities.formaldehydeMeasurement.NAME] = units.PPM, - [capabilities.veryFineDustSensor.NAME] = units.UGM3, - [capabilities.fineDustSensor.NAME] = units.UGM3, - [capabilities.dustSensor.NAME] = units.UGM3, - [capabilities.radonMeasurement.NAME] = units.BQM3, - [capabilities.tvocMeasurement.NAME] = units.PPB -- TVOC is typically within the range of 0-5500 ppb, with good to moderate values being < 660 ppb -} - --- All ConcentrationMesurement clusters inherit from the same base cluster definitions, --- so CarbonMonoxideConcentratinMeasurement is used below but the same enum types exist --- in all ConcentrationMeasurement clusters -local level_strings = { - [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.UNKNOWN] = "unknown", - [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.LOW] = "good", - [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.MEDIUM] = "moderate", - [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.HIGH] = "unhealthy", - [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.CRITICAL] = "hazardous", -} - --- measured in g/mol -local molecular_weights = { - [capabilities.carbonDioxideMeasurement.NAME] = 44.010, - [capabilities.nitrogenDioxideMeasurement.NAME] = 28.014, - [capabilities.ozoneMeasurement.NAME] = 48.0, - [capabilities.formaldehydeMeasurement.NAME] = 30.031, - [capabilities.veryFineDustSensor.NAME] = "N/A", - [capabilities.fineDustSensor.NAME] = "N/A", - [capabilities.dustSensor.NAME] = "N/A", - [capabilities.radonMeasurement.NAME] = 222.018, - [capabilities.tvocMeasurement.NAME] = "N/A", -} - -local conversion_tables = { - [units.PPM] = { - [units.PPM] = function(value) return utils.round(value) end, - [units.PPB] = function(value) return utils.round(value * (10^3)) end, - [units.UGM3] = function(value, molecular_weight) return utils.round((value * molecular_weight * 10^3) / MGM3_PPM_CONVERSION_FACTOR) end, - [units.MGM3] = function(value, molecular_weight) return utils.round((value * molecular_weight) / MGM3_PPM_CONVERSION_FACTOR) end, - }, - [units.PPB] = { - [units.PPM] = function(value) return utils.round(value/(10^3)) end, - [units.PPB] = function(value) return utils.round(value) end, - }, - [units.PPT] = { - [units.PPM] = function(value) return utils.round(value/(10^6)) end - }, - [units.MGM3] = { - [units.UGM3] = function(value) return utils.round(value * (10^3)) end, - [units.PPM] = function(value, molecular_weight) return utils.round((value * MGM3_PPM_CONVERSION_FACTOR) / molecular_weight) end, - }, - [units.UGM3] = { - [units.UGM3] = function(value) return utils.round(value) end, - [units.PPM] = function(value, molecular_weight) return utils.round((value * MGM3_PPM_CONVERSION_FACTOR) / (molecular_weight * 10^3)) end, - }, - [units.NGM3] = { - [units.UGM3] = function(value) return utils.round(value/(10^3)) end - }, - [units.BQM3] = { - [units.PCIL] = function(value) return utils.round(value/37) end - }, -} - -local function unit_conversion(value, from_unit, to_unit, capability_name) - local conversion_function = conversion_tables[from_unit][to_unit] - if not conversion_function then - log.info_with( {hub_logs = true} , string.format("Unsupported unit conversion from %s to %s", unit_strings[from_unit], unit_strings[to_unit])) - return - end - - if not value then - log.info_with( {hub_logs = true} , "unit conversion value is nil") - return - end - - return conversion_function(value, molecular_weights[capability_name]) -end - -local function measurementHandlerFactory(capability_name, attribute, target_unit) - return function(driver, device, ib, response) - local reporting_unit = device:get_field(capability_name.."_unit") - - if not reporting_unit then - reporting_unit = unit_default[capability_name] - device:set_field(capability_name.."_unit", reporting_unit, {persist = true}) - end - - local value = nil - if reporting_unit then - value = unit_conversion(ib.data.value, reporting_unit, target_unit, capability_name) - end - - if value then - device:emit_event_for_endpoint(ib.endpoint_id, attribute({value = value, unit = unit_strings[target_unit]})) - -- handle case where device profile supports both fineDustLevel and dustLevel - if capability_name == capabilities.fineDustSensor.NAME and device:supports_capability(capabilities.dustSensor) then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.dustSensor.fineDustLevel({value = value, unit = unit_strings[target_unit]})) - end - end - end -end - -local function levelHandlerFactory(attribute) - return function(driver, device, ib, response) - device:emit_event_for_endpoint(ib.endpoint_id, attribute(level_strings[ib.data.value])) - end -end - --- handlers -local function air_quality_attr_handler(driver, device, ib, response) - local state = ib.data.value - if state == 0 then -- Unknown - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.unknown()) - elseif state == 1 then -- Good - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.good()) - elseif state == 2 then -- Fair - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.moderate()) - elseif state == 3 then -- Moderate - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.slightlyUnhealthy()) - elseif state == 4 then -- Poor - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.unhealthy()) - elseif state == 5 then -- VeryPoor - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.veryUnhealthy()) - elseif state == 6 then -- ExtremelyPoor - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.hazardous()) - end -end - -local function on_off_attr_handler(driver, device, ib, response) - if ib.data.value then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.on()) - else - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.off()) - end -end - -local function temp_event_handler(attribute) - return function(driver, device, ib, response) - if ib.data.value == nil then - return - end - local unit = "C" - - -- Only emit the capability for RPC version >= 5, since unit conversion for - -- range capabilities is only supported in that case. - if version.rpc >= 5 then - local event - if attribute == capabilities.thermostatCoolingSetpoint.coolingSetpoint then - local range = { - minimum = device:get_field(setpoint_limit_device_field.MIN_COOL) or THERMOSTAT_MIN_TEMP_IN_C, - maximum = device:get_field(setpoint_limit_device_field.MAX_COOL) or THERMOSTAT_MAX_TEMP_IN_C, - step = 0.1 - } - event = capabilities.thermostatCoolingSetpoint.coolingSetpointRange({value = range, unit = unit}) - device:emit_event_for_endpoint(ib.endpoint_id, event) - elseif attribute == capabilities.thermostatHeatingSetpoint.heatingSetpoint then - local MAX_TEMP_IN_C = THERMOSTAT_MAX_TEMP_IN_C - local MIN_TEMP_IN_C = THERMOSTAT_MIN_TEMP_IN_C - local is_water_heater_device = get_device_type(device) == WATER_HEATER_DEVICE_TYPE_ID - if is_water_heater_device then - MAX_TEMP_IN_C = WATER_HEATER_MAX_TEMP_IN_C - MIN_TEMP_IN_C = WATER_HEATER_MIN_TEMP_IN_C - end - - local range = { - minimum = device:get_field(setpoint_limit_device_field.MIN_HEAT) or MIN_TEMP_IN_C, - maximum = device:get_field(setpoint_limit_device_field.MAX_HEAT) or MAX_TEMP_IN_C, - step = 0.1 - } - event = capabilities.thermostatHeatingSetpoint.heatingSetpointRange({value = range, unit = unit}) - device:emit_event_for_endpoint(ib.endpoint_id, event) - end - end - - local temp = ib.data.value / 100.0 - device:emit_event_for_endpoint(ib.endpoint_id, attribute({value = temp, unit = unit})) - end -end - -local temp_attr_handler_factory = function(minOrMax) - return function(driver, device, ib, response) - if ib.data.value == nil then - return - end - local temp = ib.data.value / 100.0 - local unit = "C" - temp = utils.clamp_value(temp, THERMOSTAT_MIN_TEMP_IN_C, THERMOSTAT_MAX_TEMP_IN_C) - set_field_for_endpoint(device, minOrMax, ib.endpoint_id, temp) - local min = get_field_for_endpoint(device, setpoint_limit_device_field.MIN_TEMP, ib.endpoint_id) - local max = get_field_for_endpoint(device, setpoint_limit_device_field.MAX_TEMP, ib.endpoint_id) - if min ~= nil and max ~= nil then - if min < max then - -- Only emit the capability for RPC version >= 5 (unit conversion for - -- temperature range capability is only supported for RPC >= 5) - if version.rpc >= 5 then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = min, maximum = max }, unit = unit })) - end - set_field_for_endpoint(device, setpoint_limit_device_field.MIN_TEMP, ib.endpoint_id, nil) - set_field_for_endpoint(device, setpoint_limit_device_field.MAX_TEMP, ib.endpoint_id, nil) - else - device.log.warn_with({hub_logs = true}, string.format("Device reported a min temperature %d that is not lower than the reported max temperature %d", min, max)) - end - end - end -end - -local function humidity_attr_handler(driver, device, ib, response) - local humidity = math.floor(ib.data.value / 100.0) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.relativeHumidityMeasurement.humidity(humidity)) -end - -local function system_mode_handler(driver, device, ib, response) - if device:get_field(OPTIONAL_THERMOSTAT_MODES_SEEN) == nil then -- this being nil means the sequence_of_operation_handler hasn't run. - device.log.info_with({hub_logs = true}, "In the SystemMode handler: ControlSequenceOfOperation has not run yet. Exiting early.") - device:set_field(SAVED_SYSTEM_MODE_IB, ib) - return - end - - local supported_modes = device:get_latest_state(device:endpoint_to_component(ib.endpoint_id), capabilities.thermostatMode.ID, capabilities.thermostatMode.supportedThermostatModes.NAME) or {} - -- check that the given mode was in the supported modes list - if tbl_contains(supported_modes, THERMOSTAT_MODE_MAP[ib.data.value].NAME) then - device:emit_event_for_endpoint(ib.endpoint_id, THERMOSTAT_MODE_MAP[ib.data.value]()) - return - end - -- if the value is not found in the supported modes list, check if it's disallowed and early return if so. - local disallowed_thermostat_modes = device:get_field(DISALLOWED_THERMOSTAT_MODES) or {} - if tbl_contains(disallowed_thermostat_modes, THERMOSTAT_MODE_MAP[ib.data.value].NAME) then - return - end - -- if we get here, then the reported mode is allowed and not in our mode map - -- add the mode to the OPTIONAL_THERMOSTAT_MODES_SEEN and supportedThermostatModes tables - local optional_modes_seen = utils.deep_copy(device:get_field(OPTIONAL_THERMOSTAT_MODES_SEEN)) or {} - table.insert(optional_modes_seen, THERMOSTAT_MODE_MAP[ib.data.value].NAME) - device:set_field(OPTIONAL_THERMOSTAT_MODES_SEEN, optional_modes_seen, {persist=true}) - local sm_copy = utils.deep_copy(supported_modes) - table.insert(sm_copy, THERMOSTAT_MODE_MAP[ib.data.value].NAME) - local supported_modes_event = capabilities.thermostatMode.supportedThermostatModes(sm_copy, {visibility = {displayed = false}}) - device:emit_event_for_endpoint(ib.endpoint_id, supported_modes_event) - device:emit_event_for_endpoint(ib.endpoint_id, THERMOSTAT_MODE_MAP[ib.data.value]()) -end - -local function running_state_handler(driver, device, ib, response) - for mode, operating_state in pairs(THERMOSTAT_OPERATING_MODE_MAP) do - if ((ib.data.value >> mode) & 1) > 0 then - device:emit_event_for_endpoint(ib.endpoint_id, operating_state()) - return - end - end - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.thermostatOperatingState.thermostatOperatingState.idle()) -end - -local function sequence_of_operation_handler(driver, device, ib, response) - -- The ControlSequenceOfOperation attribute only directly specifies what can't be operated by the operating environment, not what can. - -- However, we assert here that a Cooling enum value implies that SystemMode supports cooling, and the same for a Heating enum. - -- We also assert that Off is supported, though per spec this is optional. - if device:get_field(OPTIONAL_THERMOSTAT_MODES_SEEN) == nil then - device:set_field(OPTIONAL_THERMOSTAT_MODES_SEEN, {capabilities.thermostatMode.thermostatMode.off.NAME}, {persist=true}) - end - local supported_modes = utils.deep_copy(device:get_field(OPTIONAL_THERMOSTAT_MODES_SEEN)) - local disallowed_mode_operations = {} - - local modes_for_inclusion = {} - if ib.data.value <= clusters.Thermostat.attributes.ControlSequenceOfOperation.COOLING_WITH_REHEAT then - local _, found_idx = tbl_contains(supported_modes, capabilities.thermostatMode.thermostatMode.emergency_heat.NAME) - if found_idx then - table.remove(supported_modes, found_idx) -- if seen before, remove now - end - table.insert(supported_modes, capabilities.thermostatMode.thermostatMode.cool.NAME) - table.insert(disallowed_mode_operations, capabilities.thermostatMode.thermostatMode.heat.NAME) - table.insert(disallowed_mode_operations, capabilities.thermostatMode.thermostatMode.emergency_heat.NAME) - elseif ib.data.value <= clusters.Thermostat.attributes.ControlSequenceOfOperation.HEATING_WITH_REHEAT then - local _, found_idx = tbl_contains(supported_modes, capabilities.thermostatMode.thermostatMode.precooling.NAME) - if found_idx then - table.remove(supported_modes, found_idx) -- if seen before, remove now - end - table.insert(supported_modes, capabilities.thermostatMode.thermostatMode.heat.NAME) - table.insert(disallowed_mode_operations, capabilities.thermostatMode.thermostatMode.cool.NAME) - table.insert(disallowed_mode_operations, capabilities.thermostatMode.thermostatMode.precooling.NAME) - elseif ib.data.value <= clusters.Thermostat.attributes.ControlSequenceOfOperation.COOLING_AND_HEATING_WITH_REHEAT then - table.insert(modes_for_inclusion, capabilities.thermostatMode.thermostatMode.cool.NAME) - table.insert(modes_for_inclusion, capabilities.thermostatMode.thermostatMode.heat.NAME) - end - - -- check whether the Auto Mode should be supported in SystemMode, though this is unrelated to ControlSequenceOfOperation - local auto = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.AUTOMODE}) - if #auto > 0 then - table.insert(modes_for_inclusion, capabilities.thermostatMode.thermostatMode.auto.NAME) - else - table.insert(disallowed_mode_operations, capabilities.thermostatMode.thermostatMode.auto.NAME) - end - - -- if a disallowed value was once allowed and added, it should be removed now. - for index, mode in pairs(supported_modes) do - if tbl_contains(disallowed_mode_operations, mode) then - table.remove(supported_modes, index) - end - end - -- do not include any values twice - for _, mode in pairs(modes_for_inclusion) do - if not tbl_contains(supported_modes, mode) then - table.insert(supported_modes, mode) - end - end - device:set_field(DISALLOWED_THERMOSTAT_MODES, disallowed_mode_operations) - local event = capabilities.thermostatMode.supportedThermostatModes(supported_modes, {visibility = {displayed = false}}) - device:emit_event_for_endpoint(ib.endpoint_id, event) - - -- will be set by the SystemMode handler if this handler hasn't run yet. - if device:get_field(SAVED_SYSTEM_MODE_IB) then - system_mode_handler(driver, device, device:get_field(SAVED_SYSTEM_MODE_IB), response) - device:set_field(SAVED_SYSTEM_MODE_IB, nil) - end -end - -local function min_deadband_limit_handler(driver, device, ib, response) - local val = ib.data.value / 10.0 - log.info("Setting " .. setpoint_limit_device_field.MIN_DEADBAND .. " to " .. string.format("%s", val)) - device:set_field(setpoint_limit_device_field.MIN_DEADBAND, val, { persist = true }) - device:set_field(setpoint_limit_device_field.MIN_SETPOINT_DEADBAND_CHECKED, true, {persist = true}) -end - -local function fan_mode_handler(driver, device, ib, response) - local fan_mode_event = { - [clusters.FanControl.attributes.FanMode.OFF] = { capabilities.fanMode.fanMode.off(), - capabilities.airConditionerFanMode.fanMode("off"), - capabilities.airPurifierFanMode.airPurifierFanMode.off(), - nil }, -- 'OFF' is not supported by thermostatFanMode - [clusters.FanControl.attributes.FanMode.LOW] = { capabilities.fanMode.fanMode.low(), - capabilities.airConditionerFanMode.fanMode("low"), - capabilities.airPurifierFanMode.airPurifierFanMode.low(), - capabilities.thermostatFanMode.thermostatFanMode.on() }, - [clusters.FanControl.attributes.FanMode.MEDIUM] = { capabilities.fanMode.fanMode.medium(), - capabilities.airConditionerFanMode.fanMode("medium"), - capabilities.airPurifierFanMode.airPurifierFanMode.medium(), - capabilities.thermostatFanMode.thermostatFanMode.on() }, - [clusters.FanControl.attributes.FanMode.HIGH] = { capabilities.fanMode.fanMode.high(), - capabilities.airConditionerFanMode.fanMode("high"), - capabilities.airPurifierFanMode.airPurifierFanMode.high(), - capabilities.thermostatFanMode.thermostatFanMode.on() }, - [clusters.FanControl.attributes.FanMode.ON] = { capabilities.fanMode.fanMode.auto(), - capabilities.airConditionerFanMode.fanMode("auto"), - capabilities.airPurifierFanMode.airPurifierFanMode.auto(), - capabilities.thermostatFanMode.thermostatFanMode.on() }, - [clusters.FanControl.attributes.FanMode.AUTO] = { capabilities.fanMode.fanMode.auto(), - capabilities.airConditionerFanMode.fanMode("auto"), - capabilities.airPurifierFanMode.airPurifierFanMode.auto(), - capabilities.thermostatFanMode.thermostatFanMode.auto() }, - [clusters.FanControl.attributes.FanMode.SMART] = { capabilities.fanMode.fanMode.auto(), - capabilities.airConditionerFanMode.fanMode("auto"), - capabilities.airPurifierFanMode.airPurifierFanMode.auto(), - capabilities.thermostatFanMode.thermostatFanMode.auto() } - } - local fan_mode_idx = device:supports_capability_by_id(capabilities.fanMode.ID) and 1 or - device:supports_capability_by_id(capabilities.airConditionerFanMode.ID) and 2 or - device:supports_capability_by_id(capabilities.airPurifierFanMode.ID) and 3 or - device:supports_capability_by_id(capabilities.thermostatFanMode.ID) and 4 - if fan_mode_idx ~= false and fan_mode_event[ib.data.value][fan_mode_idx] then - device:emit_event_for_endpoint(ib.endpoint_id, fan_mode_event[ib.data.value][fan_mode_idx]) - else - log.warn(string.format("Invalid Fan Mode (%s)", ib.data.value)) - end -end - -local function fan_mode_sequence_handler(driver, device, ib, response) - local supportedFanModes, supported_fan_modes_attribute - if ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH then - supportedFanModes = { "off", "low", "medium", "high" } - elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH then - supportedFanModes = { "off", "low", "high" } - elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH_AUTO then - supportedFanModes = { "off", "low", "medium", "high", "auto" } - elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH_AUTO then - supportedFanModes = { "off", "low", "high", "auto" } - elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_HIGH_AUTO then - supportedFanModes = { "off", "high", "auto" } - else - supportedFanModes = { "off", "high" } - end - - if device:supports_capability_by_id(capabilities.airPurifierFanMode.ID) then - supported_fan_modes_attribute = capabilities.airPurifierFanMode.supportedAirPurifierFanModes - elseif device:supports_capability_by_id(capabilities.airConditionerFanMode.ID) then - supported_fan_modes_attribute = capabilities.airConditionerFanMode.supportedAcFanModes - elseif device:supports_capability_by_id(capabilities.thermostatFanMode.ID) then - supported_fan_modes_attribute = capabilities.thermostatFanMode.supportedThermostatFanModes - -- Our thermostat fan mode control is not granular enough to handle all of the supported modes - if ib.data.value >= clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH_AUTO and - ib.data.value <= clusters.FanControl.attributes.FanModeSequence.OFF_ON_AUTO then - supportedFanModes = { "auto", "on" } - else - supportedFanModes = { "on" } - end - else - supported_fan_modes_attribute = capabilities.fanMode.supportedFanModes - end - - local event = supported_fan_modes_attribute(supportedFanModes, {visibility = {displayed = false}}) - device:emit_event_for_endpoint(ib.endpoint_id, event) -end - -local function fan_speed_percent_attr_handler(driver, device, ib, response) - local speed = 0 - if ib.data.value ~= nil then - speed = utils.clamp_value(ib.data.value, MIN_ALLOWED_PERCENT_VALUE, MAX_ALLOWED_PERCENT_VALUE) - end - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanSpeedPercent.percent(speed)) -end - -local function wind_support_handler(driver, device, ib, response) - local supported_wind_modes = {capabilities.windMode.windMode.noWind.NAME} - for mode, wind_mode in pairs(WIND_MODE_MAP) do - if ((ib.data.value >> mode) & 1) > 0 then - table.insert(supported_wind_modes, wind_mode.NAME) - end - end - local event = capabilities.windMode.supportedWindModes(supported_wind_modes, {visibility = {displayed = false}}) - device:emit_event_for_endpoint(ib.endpoint_id, event) -end - -local function wind_setting_handler(driver, device, ib, response) - for index, wind_mode in pairs(WIND_MODE_MAP) do - if ((ib.data.value >> index) & 1) > 0 then - device:emit_event_for_endpoint(ib.endpoint_id, wind_mode()) - return - end - end - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.windMode.windMode.noWind()) -end - -local function rock_support_handler(driver, device, ib, response) - local supported_rock_modes = {capabilities.fanOscillationMode.fanOscillationMode.off.NAME} - for mode, rock_mode in pairs(ROCK_MODE_MAP) do - if ((ib.data.value >> mode) & 1) > 0 then - table.insert(supported_rock_modes, rock_mode.NAME) - end - end - local event = capabilities.fanOscillationMode.supportedFanOscillationModes(supported_rock_modes, {visibility = {displayed = false}}) - device:emit_event_for_endpoint(ib.endpoint_id, event) -end - -local function rock_setting_handler(driver, device, ib, response) - for index, rock_mode in pairs(ROCK_MODE_MAP) do - if ((ib.data.value >> index) & 1) > 0 then - device:emit_event_for_endpoint(ib.endpoint_id, rock_mode()) - return - end - end - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanOscillationMode.fanOscillationMode.off()) -end - -local function hepa_filter_condition_handler(driver, device, ib, response) - local component = device.profile.components["hepaFilter"] - local condition = ib.data.value - device:emit_component_event(component, capabilities.filterState.filterLifeRemaining(condition)) -end - -local function hepa_filter_change_indication_handler(driver, device, ib, response) - local component = device.profile.components["hepaFilter"] - if ib.data.value == clusters.HepaFilterMonitoring.attributes.ChangeIndication.OK then - device:emit_component_event(component, capabilities.filterStatus.filterStatus.normal()) - elseif ib.data.value == clusters.HepaFilterMonitoring.attributes.ChangeIndication.WARNING then - device:emit_component_event(component, capabilities.filterStatus.filterStatus.normal()) - elseif ib.data.value == clusters.HepaFilterMonitoring.attributes.ChangeIndication.CRITICAL then - device:emit_component_event(component, capabilities.filterStatus.filterStatus.replace()) - end -end - -local function activated_carbon_filter_condition_handler(driver, device, ib, response) - local component = device.profile.components["activatedCarbonFilter"] - local condition = ib.data.value - device:emit_component_event(component, capabilities.filterState.filterLifeRemaining(condition)) -end - -local function activated_carbon_filter_change_indication_handler(driver, device, ib, response) - local component = device.profile.components["activatedCarbonFilter"] - if ib.data.value == clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.OK then - device:emit_component_event(component, capabilities.filterStatus.filterStatus.normal()) - elseif ib.data.value == clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.WARNING then - device:emit_component_event(component, capabilities.filterStatus.filterStatus.normal()) - elseif ib.data.value == clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.CRITICAL then - device:emit_component_event(component, capabilities.filterStatus.filterStatus.replace()) - end -end - -local function handle_switch_on(driver, device, cmd) - local endpoint_id = component_to_endpoint(device, cmd.component, clusters.OnOff.ID) - local req = clusters.OnOff.server.commands.On(device, endpoint_id) - device:send(req) -end - -local function handle_switch_off(driver, device, cmd) - local endpoint_id = component_to_endpoint(device, cmd.component, clusters.OnOff.ID) - local req = clusters.OnOff.server.commands.Off(device, endpoint_id) - device:send(req) -end +local version = require "version" +local MatterDriver = require "st.matter.driver" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local im = require "st.matter.interaction_model" +local attribute_handlers = require "thermostat_handlers.attribute_handlers" +local capability_handlers = require "thermostat_handlers.capability_handlers" +local fields = require "thermostat_utils.fields" +local thermostat_utils = require "thermostat_utils.utils" +local embedded_cluster_utils = require "thermostat_utils.embedded_cluster_utils" -local function set_thermostat_mode(driver, device, cmd) - local mode_id = nil - for value, mode in pairs(THERMOSTAT_MODE_MAP) do - if mode.NAME == cmd.args.mode then - mode_id = value - break - end - end - if mode_id then - device:send(clusters.Thermostat.attributes.SystemMode:write(device, component_to_endpoint(device, cmd.component, clusters.Thermostat.ID), mode_id)) - end +if version.api < 10 then + clusters.HepaFilterMonitoring = require "embedded_clusters.HepaFilterMonitoring" + clusters.ActivatedCarbonFilterMonitoring = require "embedded_clusters.ActivatedCarbonFilterMonitoring" + clusters.AirQuality = require "embedded_clusters.AirQuality" + clusters.CarbonMonoxideConcentrationMeasurement = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement" + clusters.CarbonDioxideConcentrationMeasurement = require "embedded_clusters.CarbonDioxideConcentrationMeasurement" + clusters.FormaldehydeConcentrationMeasurement = require "embedded_clusters.FormaldehydeConcentrationMeasurement" + clusters.NitrogenDioxideConcentrationMeasurement = require "embedded_clusters.NitrogenDioxideConcentrationMeasurement" + clusters.OzoneConcentrationMeasurement = require "embedded_clusters.OzoneConcentrationMeasurement" + clusters.Pm1ConcentrationMeasurement = require "embedded_clusters.Pm1ConcentrationMeasurement" + clusters.Pm10ConcentrationMeasurement = require "embedded_clusters.Pm10ConcentrationMeasurement" + clusters.Pm25ConcentrationMeasurement = require "embedded_clusters.Pm25ConcentrationMeasurement" + clusters.RadonConcentrationMeasurement = require "embedded_clusters.RadonConcentrationMeasurement" + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement" end -local thermostat_mode_setter = function(mode_name) - return function(driver, device, cmd) - return set_thermostat_mode(driver, device, {component = cmd.component, args = {mode = mode_name}}) - end +if version.api < 11 then + clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement" + clusters.ElectricalPowerMeasurement = require "embedded_clusters.ElectricalPowerMeasurement" end -local function set_setpoint(setpoint) - return function(driver, device, cmd) - local endpoint_id = component_to_endpoint(device, cmd.component, clusters.Thermostat.ID) - local MAX_TEMP_IN_C = THERMOSTAT_MAX_TEMP_IN_C - local MIN_TEMP_IN_C = THERMOSTAT_MIN_TEMP_IN_C - local is_water_heater_device = get_device_type(device) == WATER_HEATER_DEVICE_TYPE_ID - if is_water_heater_device then - MAX_TEMP_IN_C = WATER_HEATER_MAX_TEMP_IN_C - MIN_TEMP_IN_C = WATER_HEATER_MIN_TEMP_IN_C - end - local value = cmd.args.setpoint - if version.rpc <= 5 and value > MAX_TEMP_IN_C then - value = utils.f_to_c(value) - end - - -- Gather cached setpoint values when considering setpoint limits - -- Note: cached values should always exist, but defaults are chosen just in case to prevent - -- nil operation errors, and deadband logic from triggering. - local cached_cooling_val, cooling_setpoint = device:get_latest_state( - cmd.component, capabilities.thermostatCoolingSetpoint.ID, - capabilities.thermostatCoolingSetpoint.coolingSetpoint.NAME, - MAX_TEMP_IN_C, { value = MAX_TEMP_IN_C, unit = "C" } - ) - if cooling_setpoint and cooling_setpoint.unit == "F" then - cached_cooling_val = utils.f_to_c(cached_cooling_val) - end - local cached_heating_val, heating_setpoint = device:get_latest_state( - cmd.component, capabilities.thermostatHeatingSetpoint.ID, - capabilities.thermostatHeatingSetpoint.heatingSetpoint.NAME, - MIN_TEMP_IN_C, { value = MIN_TEMP_IN_C, unit = "C" } - ) - if heating_setpoint and heating_setpoint.unit == "F" then - cached_heating_val = utils.f_to_c(cached_heating_val) - end - local is_auto_capable = #device:get_endpoints( - clusters.Thermostat.ID, - {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.AUTOMODE} - ) > 0 - - --Check setpoint limits for the device - local setpoint_type = string.match(setpoint.NAME, "Heat") or "Cool" - local deadband = device:get_field(setpoint_limit_device_field.MIN_DEADBAND) or 2.5 --spec default - if setpoint_type == "Heat" then - local min = device:get_field(setpoint_limit_device_field.MIN_HEAT) or MIN_TEMP_IN_C - local max = device:get_field(setpoint_limit_device_field.MAX_HEAT) or MAX_TEMP_IN_C - if value < min or value > max then - log.warn(string.format( - "Invalid setpoint (%s) outside the min (%s) and the max (%s)", - value, min, max - )) - device:emit_event_for_endpoint(endpoint_id, capabilities.thermostatHeatingSetpoint.heatingSetpoint(heating_setpoint, {state_change = true})) - return - end - if is_auto_capable and value > (cached_cooling_val - deadband) then - log.warn(string.format( - "Invalid setpoint (%s) is greater than the cooling setpoint (%s) with the deadband (%s)", - value, cooling_setpoint, deadband - )) - device:emit_event_for_endpoint(endpoint_id, capabilities.thermostatHeatingSetpoint.heatingSetpoint(heating_setpoint, {state_change = true})) - return - end - else - local min = device:get_field(setpoint_limit_device_field.MIN_COOL) or MIN_TEMP_IN_C - local max = device:get_field(setpoint_limit_device_field.MAX_COOL) or MAX_TEMP_IN_C - if value < min or value > max then - log.warn(string.format( - "Invalid setpoint (%s) outside the min (%s) and the max (%s)", - value, min, max - )) - device:emit_event_for_endpoint(endpoint_id, capabilities.thermostatCoolingSetpoint.coolingSetpoint(cooling_setpoint, {state_change = true})) - return - end - if is_auto_capable and value < (cached_heating_val + deadband) then - log.warn(string.format( - "Invalid setpoint (%s) is less than the heating setpoint (%s) with the deadband (%s)", - value, heating_setpoint, deadband - )) - device:emit_event_for_endpoint(endpoint_id, capabilities.thermostatCoolingSetpoint.coolingSetpoint(cooling_setpoint, {state_change = true})) - return - end - end - device:send(setpoint:write(device, component_to_endpoint(device, cmd.component, clusters.Thermostat.ID), utils.round(value * 100.0))) - end +if version.api < 13 then + clusters.WaterHeaterMode = require "embedded_clusters.WaterHeaterMode" end -local heating_setpoint_limit_handler_factory = function(minOrMax) - return function(driver, device, ib, response) - if ib.data.value == nil then - return - end - local MAX_TEMP_IN_C = THERMOSTAT_MAX_TEMP_IN_C - local MIN_TEMP_IN_C = THERMOSTAT_MIN_TEMP_IN_C - local is_water_heater_device = (get_device_type(device) == WATER_HEATER_DEVICE_TYPE_ID) - if is_water_heater_device then - MAX_TEMP_IN_C = WATER_HEATER_MAX_TEMP_IN_C - MIN_TEMP_IN_C = WATER_HEATER_MIN_TEMP_IN_C - end - local val = ib.data.value / 100.0 - val = utils.clamp_value(val, MIN_TEMP_IN_C, MAX_TEMP_IN_C) - device:set_field(minOrMax, val) - local min = device:get_field(setpoint_limit_device_field.MIN_HEAT) - local max = device:get_field(setpoint_limit_device_field.MAX_HEAT) - if min ~= nil and max ~= nil then - if min < max then - -- Only emit the capability for RPC version >= 5 (unit conversion for - -- heating setpoint range capability is only supported for RPC >= 5) - if version.rpc >= 5 then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.thermostatHeatingSetpoint.heatingSetpointRange({ value = { minimum = min, maximum = max, step = 0.1 }, unit = "C" })) - end - else - device.log.warn_with({hub_logs = true}, string.format("Device reported a min heating setpoint %d that is not lower than the reported max %d", min, max)) - end - end - end -end +local ThermostatLifecycleHandlers = {} -local cooling_setpoint_limit_handler_factory = function(minOrMax) - return function(driver, device, ib, response) - if ib.data.value == nil then - return - end - local val = ib.data.value / 100.0 - val = utils.clamp_value(val, THERMOSTAT_MIN_TEMP_IN_C, THERMOSTAT_MAX_TEMP_IN_C) - device:set_field(minOrMax, val) - local min = device:get_field(setpoint_limit_device_field.MIN_COOL) - local max = device:get_field(setpoint_limit_device_field.MAX_COOL) - if min ~= nil and max ~= nil then - if min < max then - -- Only emit the capability for RPC version >= 5 (unit conversion for - -- cooling setpoint range capability is only supported for RPC >= 5) - if version.rpc >= 5 then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.thermostatCoolingSetpoint.coolingSetpointRange({ value = { minimum = min, maximum = max, step = 0.1 }, unit = "C" })) - end - else - device.log.warn_with({hub_logs = true}, string.format("Device reported a min cooling setpoint %d that is not lower than the reported max %d", min, max)) - end - end - end -end +function ThermostatLifecycleHandlers.device_added(driver, device) + local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) + req:merge(clusters.Thermostat.attributes.ControlSequenceOfOperation:read(device)) + req:merge(clusters.FanControl.attributes.FanModeSequence:read(device)) + req:merge(clusters.FanControl.attributes.WindSupport:read(device)) + req:merge(clusters.FanControl.attributes.RockSupport:read(device)) -local function set_fan_mode(device, cmd, fan_mode_capability) - local command_argument = cmd.args.fanMode - if fan_mode_capability == capabilities.airPurifierFanMode then - command_argument = cmd.args.airPurifierFanMode - elseif fan_mode_capability == capabilities.thermostatFanMode then - command_argument = cmd.args.mode - end - local fan_mode_id - if command_argument == "off" then - fan_mode_id = clusters.FanControl.attributes.FanMode.OFF - elseif command_argument == "on" then - fan_mode_id = clusters.FanControl.attributes.FanMode.ON - elseif command_argument == "auto" then - fan_mode_id = clusters.FanControl.attributes.FanMode.AUTO - elseif command_argument == "high" then - fan_mode_id = clusters.FanControl.attributes.FanMode.HIGH - elseif command_argument == "medium" then - fan_mode_id = clusters.FanControl.attributes.FanMode.MEDIUM - elseif tbl_contains({ "low", "sleep", "quiet", "windFree" }, command_argument) then - fan_mode_id = clusters.FanControl.attributes.FanMode.LOW + local thermostat_eps = device:get_endpoints(clusters.Thermostat.ID) + if #thermostat_eps > 0 then + req:merge(clusters.Thermostat.attributes.AttributeList:read(device)) else - device.log.warn(string.format("Invalid Fan Mode (%s) received from capability command", command_argument)) - return - end - device:send(clusters.FanControl.attributes.FanMode:write(device, component_to_endpoint(device, cmd.component, clusters.FanControl.ID), fan_mode_id)) -end - -local set_fan_mode_factory = function(fan_mode_capability) - return function(driver, device, cmd) - set_fan_mode(device, cmd, fan_mode_capability) - end -end - -local function thermostat_fan_mode_setter(mode_name) - return function(driver, device, cmd) - set_fan_mode(device, {component = cmd.component, args = {mode = mode_name}}, capabilities.thermostatFanMode) - end -end - -local function set_fan_speed_percent(driver, device, cmd) - local speed = math.floor(cmd.args.percent) - device:send(clusters.FanControl.attributes.PercentSetting:write(device, component_to_endpoint(device, cmd.component, clusters.FanControl.ID), speed)) -end - -local function set_wind_mode(driver, device, cmd) - local wind_mode = 0 - if cmd.args.windMode == capabilities.windMode.windMode.sleepWind.NAME then - wind_mode = clusters.FanControl.types.WindSupportMask.SLEEP_WIND - elseif cmd.args.windMode == capabilities.windMode.windMode.naturalWind.NAME then - wind_mode = clusters.FanControl.types.WindSupportMask.NATURAL_WIND - end - device:send(clusters.FanControl.attributes.WindSetting:write(device, component_to_endpoint(device, cmd.component, clusters.FanControl.ID), wind_mode)) -end - -local function set_rock_mode(driver, device, cmd) - local rock_mode = 0 - if cmd.args.fanOscillationMode == capabilities.fanOscillationMode.fanOscillationMode.horizontal.NAME then - rock_mode = clusters.FanControl.types.RockSupportMask.ROCK_LEFT_RIGHT - elseif cmd.args.fanOscillationMode == capabilities.fanOscillationMode.fanOscillationMode.vertical.NAME then - rock_mode = clusters.FanControl.types.RockSupportMask.ROCK_UP_DOWN - elseif cmd.args.fanOscillationMode == capabilities.fanOscillationMode.fanOscillationMode.swing.NAME then - rock_mode = clusters.FanControl.types.RockSupportMask.ROCK_ROUND - end - device:send(clusters.FanControl.attributes.RockSetting:write(device, component_to_endpoint(device, cmd.component, clusters.FanControl.ID), rock_mode)) -end - -local function set_water_heater_mode(driver, device, cmd) - device.log.info(string.format("set_water_heater_mode mode: %s", cmd.args.mode)) - local endpoint_id = component_to_endpoint(device, cmd.component, clusters.Thermostat.ID) - local supportedWaterHeaterModesWithIdx = device:get_field(SUPPORTED_WATER_HEATER_MODES_WITH_IDX) or {} - for i, mode in ipairs(supportedWaterHeaterModesWithIdx) do - if cmd.args.mode == mode[2] then - device:send(clusters.WaterHeaterMode.commands.ChangeToMode(device, endpoint_id, mode[1])) - return - end + device:set_field(fields.profiling_data.THERMOSTAT_RUNNING_STATE_SUPPORT, false) end -end - -local function reset_filter_state(driver, device, cmd) - local endpoint_id = device:component_to_endpoint(cmd.component) - if cmd.component == "hepaFilter" then - device:send(clusters.HepaFilterMonitoring.server.commands.ResetCondition(device, endpoint_id)) + local battery_feature_eps = device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) + if #battery_feature_eps > 0 then + req:merge(clusters.PowerSource.attributes.AttributeList:read(device)) else - device:send(clusters.ActivatedCarbonFilterMonitoring.server.commands.ResetCondition(device, endpoint_id)) - end -end - -local function battery_percent_remaining_attr_handler(driver, device, ib, response) - if ib.data.value then - device:emit_event(capabilities.battery.battery(math.floor(ib.data.value / 2.0 + 0.5))) + device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.NO_BATTERY) end -end - -local function active_power_handler(driver, device, ib, response) - if ib.data.value then - local watt_value = ib.data.value / 1000 - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.powerMeter.power({ value = watt_value, unit = "W" })) - if type(device.register_native_capability_attr_handler) == "function" then - device:register_native_capability_attr_handler("powerMeter","power") - end + device:send(req) + local heat_pump_eps = thermostat_utils.get_endpoints_by_device_type(device, fields.HEAT_PUMP_DEVICE_TYPE_ID) or {} + if #heat_pump_eps > 0 then + local thermostat_eps = thermostat_utils.get_endpoints_by_device_type(device, fields.THERMOSTAT_DEVICE_TYPE_ID) or {} + local component_to_endpoint_map = { + ["thermostatOne"] = thermostat_eps[1], + ["thermostatTwo"] = thermostat_eps[2], + } + device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, component_to_endpoint_map, {persist = true}) end end -local function periodic_energy_imported_handler(driver, device, ib, response) - if ib.data then - if version.api < 11 then - clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:augment_type(ib.data) - end - local endpoint_id = string.format(ib.endpoint_id) - local energy_imported_Wh = utils.round(ib.data.elements.energy.value / 1000) --convert mWh to Wh - local cumulative_energy_imported = device:get_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP) or {} - cumulative_energy_imported[endpoint_id] = cumulative_energy_imported[endpoint_id] or 0 - cumulative_energy_imported[endpoint_id] = cumulative_energy_imported[endpoint_id] + energy_imported_Wh - device:set_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP, cumulative_energy_imported, { persist = true }) - local total_cumulative_energy_imported = get_total_cumulative_energy_imported(device) - device:emit_component_event(device.profile.components["main"], ib.endpoint_id, capabilities.energyMeter.energy({value = total_cumulative_energy_imported, unit = "Wh"})) - report_power_consumption_to_st_energy(device, total_cumulative_energy_imported) - end +function ThermostatLifecycleHandlers.do_configure(driver, device) + local device_cfg = require "thermostat_utils.device_configuration" + device_cfg.match_profile(device) end -local function cumulative_energy_imported_handler(driver, device, ib, response) - if ib.data then - if version.api < 11 then - clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:augment_type(ib.data) - end - local endpoint_id = string.format(ib.endpoint_id) - local cumulative_energy_imported = device:get_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP) or {} - local cumulative_energy_imported_Wh = utils.round( ib.data.elements.energy.value / 1000) -- convert mWh to Wh - cumulative_energy_imported[endpoint_id] = cumulative_energy_imported_Wh - device:set_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP, cumulative_energy_imported, { persist = true }) - local total_cumulative_energy_imported = get_total_cumulative_energy_imported(device) - device:emit_component_event(device.profile.components["main"], capabilities.energyMeter.energy({ value = total_cumulative_energy_imported, unit = "Wh" })) - report_power_consumption_to_st_energy(device, total_cumulative_energy_imported) - end +function ThermostatLifecycleHandlers.driver_switched(driver, device) + local device_cfg = require "thermostat_utils.device_configuration" + device_cfg.match_profile(device) end -local function energy_report_handler_factory(is_cumulative_report) - return function(driver, device, ib, response) - if is_cumulative_report then - cumulative_energy_imported_handler(driver, device, ib, response) - elseif device:get_field(CUMULATIVE_REPORTS_NOT_SUPPORTED) then - periodic_energy_imported_handler(driver, device, ib, response) - end +function ThermostatLifecycleHandlers.device_init(driver, device) + if device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES) and (version.api < 15 or version.rpc < 9) then + -- assume that device is using a modular profile on 0.57 FW, override supports_capability_by_id + -- library function to utilize optional capabilities + device:extend_device("supports_capability_by_id", thermostat_utils.supports_capability_by_id_modular) end -end - -local function water_heater_supported_modes_attr_handler(driver, device, ib, response) - local supportWaterHeaterModes = {} - local supportWaterHeaterModesWithIdx = {} - for _, mode in ipairs(ib.data.elements) do - if version.api < 13 then - clusters.WaterHeaterMode.types.ModeOptionStruct:augment_type(mode) + device:subscribe() + device:set_component_to_endpoint_fn(thermostat_utils.component_to_endpoint) + device:set_endpoint_to_component_fn(thermostat_utils.endpoint_to_component) + thermostat_utils.handle_thermostat_operating_state_info(device) + if not device:get_field(fields.setpoint_limit_device_field.MIN_SETPOINT_DEADBAND_CHECKED) then + local auto_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.AUTOMODE}) + --Query min setpoint deadband if needed + if #auto_eps ~= 0 and device:get_field(fields.setpoint_limit_device_field.MIN_DEADBAND) == nil then + device:send(clusters.Thermostat.attributes.MinSetpointDeadBand:read()) end - table.insert(supportWaterHeaterModes, mode.elements.label.value) - table.insert(supportWaterHeaterModesWithIdx, {mode.elements.mode.value, mode.elements.label.value}) end - device:set_field(SUPPORTED_WATER_HEATER_MODES_WITH_IDX, supportWaterHeaterModesWithIdx, { persist = true }) - local event = capabilities.mode.supportedModes(supportWaterHeaterModes, { visibility = { displayed = false } }) - device:emit_event_for_endpoint(ib.endpoint_id, event) - event = capabilities.mode.supportedArguments(supportWaterHeaterModes, { visibility = { displayed = false } }) - device:emit_event_for_endpoint(ib.endpoint_id, event) -end -local function water_heater_mode_handler(driver, device, ib, response) - device.log.info(string.format("water_heater_mode_handler mode: %s", ib.data.value)) - local supportWaterHeaterModesWithIdx = device:get_field(SUPPORTED_WATER_HEATER_MODES_WITH_IDX) or {} - local currentMode = ib.data.value - for i, mode in ipairs(supportWaterHeaterModesWithIdx) do - if mode[1] == currentMode then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.mode.mode(mode[2])) - break - end + -- device energy reporting must be handled cumulatively, periodically, or by both simulatanously. + -- To ensure a single source of truth, we only handle a device's periodic reporting if cumulative reporting is not supported. + if #embedded_cluster_utils.get_endpoints( + device, + clusters.ElectricalEnergyMeasurement.ID, + {feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY} + ) == 0 then device:set_field(fields.CUMULATIVE_REPORTS_NOT_SUPPORTED, true, {persist = false}) end end -local function battery_charge_level_attr_handler(driver, device, ib, response) - if ib.data.value == clusters.PowerSource.types.BatChargeLevelEnum.OK then - device:emit_event(capabilities.batteryLevel.battery.normal()) - elseif ib.data.value == clusters.PowerSource.types.BatChargeLevelEnum.WARNING then - device:emit_event(capabilities.batteryLevel.battery.warning()) - elseif ib.data.value == clusters.PowerSource.types.BatChargeLevelEnum.CRITICAL then - device:emit_event(capabilities.batteryLevel.battery.critical()) +function ThermostatLifecycleHandlers.info_changed(driver, device, event, args) + if device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES) then + -- This indicates the device should be using a modular profile, so + -- re-up subscription with new capabilities using the modular supports_capability override + device:extend_device("supports_capability_by_id", thermostat_utils.supports_capability_by_id_modular) end -end -local function power_source_attribute_list_handler(driver, device, ib, response) - for _, attr in ipairs(ib.data.elements) do - -- mark if the device if BatPercentRemaining (Attribute ID 0x0C) or - -- BatChargeLevel (Attribute ID 0x0E) is present and try profiling. - if attr.value == 0x0C then - device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_PERCENTAGE) - match_profile(driver, device) - return - elseif attr.value == 0x0E then - device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_LEVEL) - match_profile(driver, device) - return - end + if device.profile.id ~= args.old_st_store.profile.id then + thermostat_utils.handle_thermostat_operating_state_info(device) + device:subscribe() end end -local function thermostat_attribute_list_handler(driver, device, ib, response) - for _, attr in ipairs(ib.data.elements) do - -- mark whether the optional attribute ThermostatRunningState (0x029) is present and try profiling - if attr.value == 0x029 then - device:set_field(profiling_data.THERMOSTAT_RUNNING_STATE_SUPPORT, true) - match_profile(driver, device) - return - end - end - device:set_field(profiling_data.THERMOSTAT_RUNNING_STATE_SUPPORT, false) - match_profile(driver, device) +function ThermostatLifecycleHandlers.device_removed(driver, device) + device.log.info("device removed") end local matter_driver_template = { lifecycle_handlers = { - init = device_init, - added = device_added, - doConfigure = do_configure, - infoChanged = info_changed, - removed = device_removed, - driverSwitched = driver_switched + added = ThermostatLifecycleHandlers.device_added, + doConfigure = ThermostatLifecycleHandlers.do_configure, + driverSwitched = ThermostatLifecycleHandlers.driver_switched, + infoChanged = ThermostatLifecycleHandlers.info_changed, + init = ThermostatLifecycleHandlers.device_init, + removed = ThermostatLifecycleHandlers.device_removed, }, matter_handlers = { attr = { - [clusters.OnOff.ID] = { - [clusters.OnOff.attributes.OnOff.ID] = on_off_attr_handler, + [clusters.ActivatedCarbonFilterMonitoring.ID] = { + [clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.ID] = attribute_handlers.activated_carbon_filter_change_indication_handler, + [clusters.ActivatedCarbonFilterMonitoring.attributes.Condition.ID] = attribute_handlers.activated_carbon_filter_condition_handler, }, - [clusters.Thermostat.ID] = { - [clusters.Thermostat.attributes.LocalTemperature.ID] = temp_event_handler(capabilities.temperatureMeasurement.temperature), - [clusters.Thermostat.attributes.OccupiedCoolingSetpoint.ID] = temp_event_handler(capabilities.thermostatCoolingSetpoint.coolingSetpoint), - [clusters.Thermostat.attributes.OccupiedHeatingSetpoint.ID] = temp_event_handler(capabilities.thermostatHeatingSetpoint.heatingSetpoint), - [clusters.Thermostat.attributes.SystemMode.ID] = system_mode_handler, - [clusters.Thermostat.attributes.ThermostatRunningState.ID] = running_state_handler, - [clusters.Thermostat.attributes.ControlSequenceOfOperation.ID] = sequence_of_operation_handler, - [clusters.Thermostat.attributes.AbsMinHeatSetpointLimit.ID] = heating_setpoint_limit_handler_factory(setpoint_limit_device_field.MIN_HEAT), - [clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit.ID] = heating_setpoint_limit_handler_factory(setpoint_limit_device_field.MAX_HEAT), - [clusters.Thermostat.attributes.AbsMinCoolSetpointLimit.ID] = cooling_setpoint_limit_handler_factory(setpoint_limit_device_field.MIN_COOL), - [clusters.Thermostat.attributes.AbsMaxCoolSetpointLimit.ID] = cooling_setpoint_limit_handler_factory(setpoint_limit_device_field.MAX_COOL), - [clusters.Thermostat.attributes.MinSetpointDeadBand.ID] = min_deadband_limit_handler, - [clusters.Thermostat.attributes.AttributeList.ID] = thermostat_attribute_list_handler, + [clusters.AirQuality.ID] = { + [clusters.AirQuality.attributes.AirQuality.ID] = attribute_handlers.air_quality_handler, + }, + [clusters.ElectricalEnergyMeasurement.ID] = { + [clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported.ID] = attribute_handlers.energy_imported_factory(true), + [clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported.ID] = attribute_handlers.energy_imported_factory(false), + }, + [clusters.ElectricalPowerMeasurement.ID] = { + [clusters.ElectricalPowerMeasurement.attributes.ActivePower.ID] = attribute_handlers.active_power_handler }, [clusters.FanControl.ID] = { - [clusters.FanControl.attributes.FanModeSequence.ID] = fan_mode_sequence_handler, - [clusters.FanControl.attributes.FanMode.ID] = fan_mode_handler, - [clusters.FanControl.attributes.PercentCurrent.ID] = fan_speed_percent_attr_handler, - [clusters.FanControl.attributes.WindSupport.ID] = wind_support_handler, - [clusters.FanControl.attributes.WindSetting.ID] = wind_setting_handler, - [clusters.FanControl.attributes.RockSupport.ID] = rock_support_handler, - [clusters.FanControl.attributes.RockSetting.ID] = rock_setting_handler, + [clusters.FanControl.attributes.FanMode.ID] = attribute_handlers.fan_mode_handler, + [clusters.FanControl.attributes.FanModeSequence.ID] = attribute_handlers.fan_mode_sequence_handler, + [clusters.FanControl.attributes.PercentCurrent.ID] = attribute_handlers.percent_current_handler, + [clusters.FanControl.attributes.RockSetting.ID] = attribute_handlers.rock_setting_handler, + [clusters.FanControl.attributes.RockSupport.ID] = attribute_handlers.rock_support_handler, + [clusters.FanControl.attributes.WindSetting.ID] = attribute_handlers.wind_setting_handler, + [clusters.FanControl.attributes.WindSupport.ID] = attribute_handlers.wind_support_handler, }, - [clusters.TemperatureMeasurement.ID] = { - [clusters.TemperatureMeasurement.attributes.MeasuredValue.ID] = temp_event_handler(capabilities.temperatureMeasurement.temperature), - [clusters.TemperatureMeasurement.attributes.MinMeasuredValue.ID] = temp_attr_handler_factory(setpoint_limit_device_field.MIN_TEMP), - [clusters.TemperatureMeasurement.attributes.MaxMeasuredValue.ID] = temp_attr_handler_factory(setpoint_limit_device_field.MAX_TEMP), + [clusters.HepaFilterMonitoring.ID] = { + [clusters.HepaFilterMonitoring.attributes.ChangeIndication.ID] = attribute_handlers.hepa_filter_change_indication_handler, + [clusters.HepaFilterMonitoring.attributes.Condition.ID] = attribute_handlers.hepa_filter_condition_handler, }, - [clusters.RelativeHumidityMeasurement.ID] = { - [clusters.RelativeHumidityMeasurement.attributes.MeasuredValue.ID] = humidity_attr_handler + [clusters.OnOff.ID] = { + [clusters.OnOff.attributes.OnOff.ID] = attribute_handlers.on_off_handler, }, [clusters.PowerSource.ID] = { - [clusters.PowerSource.attributes.AttributeList.ID] = power_source_attribute_list_handler, - [clusters.PowerSource.attributes.BatChargeLevel.ID] = battery_charge_level_attr_handler, - [clusters.PowerSource.attributes.BatPercentRemaining.ID] = battery_percent_remaining_attr_handler, + [clusters.PowerSource.attributes.AttributeList.ID] = attribute_handlers.power_source_attribute_list_handler, + [clusters.PowerSource.attributes.BatChargeLevel.ID] = attribute_handlers.bat_charge_level_handler, + [clusters.PowerSource.attributes.BatPercentRemaining.ID] = attribute_handlers.bat_percent_remaining_handler, }, - [clusters.HepaFilterMonitoring.ID] = { - [clusters.HepaFilterMonitoring.attributes.Condition.ID] = hepa_filter_condition_handler, - [clusters.HepaFilterMonitoring.attributes.ChangeIndication.ID] = hepa_filter_change_indication_handler + [clusters.RelativeHumidityMeasurement.ID] = { + [clusters.RelativeHumidityMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.relative_humidity_measured_value_handler }, - [clusters.ActivatedCarbonFilterMonitoring.ID] = { - [clusters.ActivatedCarbonFilterMonitoring.attributes.Condition.ID] = activated_carbon_filter_condition_handler, - [clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.ID] = activated_carbon_filter_change_indication_handler + [clusters.TemperatureMeasurement.ID] = { + [clusters.TemperatureMeasurement.attributes.MaxMeasuredValue.ID] = attribute_handlers.temperature_measured_value_bounds_factory(fields.setpoint_limit_device_field.MAX_TEMP), + [clusters.TemperatureMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.temperature_handler_factory(capabilities.temperatureMeasurement.temperature), + [clusters.TemperatureMeasurement.attributes.MinMeasuredValue.ID] = attribute_handlers.temperature_measured_value_bounds_factory(fields.setpoint_limit_device_field.MIN_TEMP), }, - [clusters.AirQuality.ID] = { - [clusters.AirQuality.attributes.AirQuality.ID] = air_quality_attr_handler, + [clusters.Thermostat.ID] = { + [clusters.Thermostat.attributes.AttributeList.ID] = attribute_handlers.thermostat_attribute_list_handler, + [clusters.Thermostat.attributes.AbsMaxCoolSetpointLimit.ID] = attribute_handlers.abs_cool_setpoint_limit_factory(fields.setpoint_limit_device_field.MAX_COOL), + [clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit.ID] = attribute_handlers.abs_heat_setpoint_limit_factory(fields.setpoint_limit_device_field.MAX_HEAT), + [clusters.Thermostat.attributes.AbsMinCoolSetpointLimit.ID] = attribute_handlers.abs_cool_setpoint_limit_factory(fields.setpoint_limit_device_field.MIN_COOL), + [clusters.Thermostat.attributes.AbsMinHeatSetpointLimit.ID] = attribute_handlers.abs_heat_setpoint_limit_factory(fields.setpoint_limit_device_field.MIN_HEAT), + [clusters.Thermostat.attributes.ControlSequenceOfOperation.ID] = attribute_handlers.control_sequence_of_operation_handler, + [clusters.Thermostat.attributes.LocalTemperature.ID] = attribute_handlers.temperature_handler_factory(capabilities.temperatureMeasurement.temperature), + [clusters.Thermostat.attributes.MinSetpointDeadBand.ID] = attribute_handlers.min_setpoint_deadband_handler, + [clusters.Thermostat.attributes.OccupiedCoolingSetpoint.ID] = attribute_handlers.temperature_handler_factory(capabilities.thermostatCoolingSetpoint.coolingSetpoint), + [clusters.Thermostat.attributes.OccupiedHeatingSetpoint.ID] = attribute_handlers.temperature_handler_factory(capabilities.thermostatHeatingSetpoint.heatingSetpoint), + [clusters.Thermostat.attributes.SystemMode.ID] = attribute_handlers.system_mode_handler, + [clusters.Thermostat.attributes.ThermostatRunningState.ID] = attribute_handlers.thermostat_running_state_handler, }, - [clusters.CarbonMonoxideConcentrationMeasurement.ID] = { - [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.carbonMonoxideMeasurement.NAME, capabilities.carbonMonoxideMeasurement.carbonMonoxideLevel, units.PPM), - [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.carbonMonoxideMeasurement.NAME), - [clusters.CarbonMonoxideConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.carbonMonoxideHealthConcern.carbonMonoxideHealthConcern), + [clusters.WaterHeaterMode.ID] = { + [clusters.WaterHeaterMode.attributes.CurrentMode.ID] = attribute_handlers.water_heater_current_mode_handler, + [clusters.WaterHeaterMode.attributes.SupportedModes.ID] = attribute_handlers.water_heater_supported_modes_handler }, + -- CONCENTRATION MEASUREMENT CLUSTERS -- [clusters.CarbonDioxideConcentrationMeasurement.ID] = { - [clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.carbonDioxideMeasurement.NAME, capabilities.carbonDioxideMeasurement.carbonDioxide, units.PPM), - [clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.carbonDioxideMeasurement.NAME), - [clusters.CarbonDioxideConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern), + [clusters.CarbonDioxideConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.concentration_level_value_factory(capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern), + [clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.concentration_measured_value_factory(capabilities.carbonDioxideMeasurement.NAME, capabilities.carbonDioxideMeasurement.carbonDioxide, fields.units.PPM), + [clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.concentration_measurement_unit_factory(capabilities.carbonDioxideMeasurement.NAME), + }, + [clusters.CarbonMonoxideConcentrationMeasurement.ID] = { + [clusters.CarbonMonoxideConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.concentration_level_value_factory(capabilities.carbonMonoxideHealthConcern.carbonMonoxideHealthConcern), + [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.concentration_measured_value_factory(capabilities.carbonMonoxideMeasurement.NAME, capabilities.carbonMonoxideMeasurement.carbonMonoxideLevel, fields.units.PPM), + [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.concentration_measurement_unit_factory(capabilities.carbonMonoxideMeasurement.NAME), + }, + [clusters.FormaldehydeConcentrationMeasurement.ID] = { + [clusters.FormaldehydeConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.concentration_level_value_factory(capabilities.formaldehydeHealthConcern.formaldehydeHealthConcern), + [clusters.FormaldehydeConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.concentration_measured_value_factory(capabilities.formaldehydeMeasurement.NAME, capabilities.formaldehydeMeasurement.formaldehydeLevel, fields.units.PPM), + [clusters.FormaldehydeConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.concentration_measurement_unit_factory(capabilities.formaldehydeMeasurement.NAME), }, [clusters.NitrogenDioxideConcentrationMeasurement.ID] = { - [clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.nitrogenDioxideMeasurement.NAME, capabilities.nitrogenDioxideMeasurement.nitrogenDioxide, units.PPM), - [clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.nitrogenDioxideMeasurement.NAME), - [clusters.NitrogenDioxideConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.nitrogenDioxideHealthConcern.nitrogenDioxideHealthConcern) + [clusters.NitrogenDioxideConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.concentration_level_value_factory(capabilities.nitrogenDioxideHealthConcern.nitrogenDioxideHealthConcern), + [clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.concentration_measured_value_factory(capabilities.nitrogenDioxideMeasurement.NAME, capabilities.nitrogenDioxideMeasurement.nitrogenDioxide, fields.units.PPM), + [clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.concentration_measurement_unit_factory(capabilities.nitrogenDioxideMeasurement.NAME), }, [clusters.OzoneConcentrationMeasurement.ID] = { - [clusters.OzoneConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.ozoneMeasurement.NAME, capabilities.ozoneMeasurement.ozone, units.PPM), - [clusters.OzoneConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.ozoneMeasurement.NAME), - [clusters.OzoneConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.ozoneHealthConcern.ozoneHealthConcern) - }, - [clusters.FormaldehydeConcentrationMeasurement.ID] = { - [clusters.FormaldehydeConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.formaldehydeMeasurement.NAME, capabilities.formaldehydeMeasurement.formaldehydeLevel, units.PPM), - [clusters.FormaldehydeConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.formaldehydeMeasurement.NAME), - [clusters.FormaldehydeConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.formaldehydeHealthConcern.formaldehydeHealthConcern), + [clusters.OzoneConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.concentration_level_value_factory(capabilities.ozoneHealthConcern.ozoneHealthConcern), + [clusters.OzoneConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.concentration_measured_value_factory(capabilities.ozoneMeasurement.NAME, capabilities.ozoneMeasurement.ozone, fields.units.PPM), + [clusters.OzoneConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.concentration_measurement_unit_factory(capabilities.ozoneMeasurement.NAME), }, [clusters.Pm1ConcentrationMeasurement.ID] = { - [clusters.Pm1ConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.veryFineDustSensor.NAME, capabilities.veryFineDustSensor.veryFineDustLevel, units.UGM3), - [clusters.Pm1ConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.veryFineDustSensor.NAME), - [clusters.Pm1ConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.veryFineDustHealthConcern.veryFineDustHealthConcern), - }, - [clusters.Pm25ConcentrationMeasurement.ID] = { - [clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.fineDustSensor.NAME, capabilities.fineDustSensor.fineDustLevel, units.UGM3), - [clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.fineDustSensor.NAME), - [clusters.Pm25ConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.fineDustHealthConcern.fineDustHealthConcern), + [clusters.Pm1ConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.concentration_level_value_factory(capabilities.veryFineDustHealthConcern.veryFineDustHealthConcern), + [clusters.Pm1ConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.concentration_measured_value_factory(capabilities.veryFineDustSensor.NAME, capabilities.veryFineDustSensor.veryFineDustLevel, fields.units.UGM3), + [clusters.Pm1ConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.concentration_measurement_unit_factory(capabilities.veryFineDustSensor.NAME), }, [clusters.Pm10ConcentrationMeasurement.ID] = { - [clusters.Pm10ConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.dustSensor.NAME, capabilities.dustSensor.dustLevel, units.UGM3), - [clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.dustSensor.NAME), - [clusters.Pm10ConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.dustHealthConcern.dustHealthConcern), + [clusters.Pm10ConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.concentration_level_value_factory(capabilities.dustHealthConcern.dustHealthConcern), + [clusters.Pm10ConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.concentration_measured_value_factory(capabilities.dustSensor.NAME, capabilities.dustSensor.dustLevel, fields.units.UGM3), + [clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.concentration_measurement_unit_factory(capabilities.dustSensor.NAME), + }, + [clusters.Pm25ConcentrationMeasurement.ID] = { + [clusters.Pm25ConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.concentration_level_value_factory(capabilities.fineDustHealthConcern.fineDustHealthConcern), + [clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.concentration_measured_value_factory(capabilities.fineDustSensor.NAME, capabilities.fineDustSensor.fineDustLevel, fields.units.UGM3), + [clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.concentration_measurement_unit_factory(capabilities.fineDustSensor.NAME), }, [clusters.RadonConcentrationMeasurement.ID] = { - [clusters.RadonConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.radonMeasurement.NAME, capabilities.radonMeasurement.radonLevel, units.PCIL), - [clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.radonMeasurement.NAME), - [clusters.RadonConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.radonHealthConcern.radonHealthConcern) + [clusters.RadonConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.concentration_level_value_factory(capabilities.radonHealthConcern.radonHealthConcern), + [clusters.RadonConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.concentration_measured_value_factory(capabilities.radonMeasurement.NAME, capabilities.radonMeasurement.radonLevel, fields.units.PCIL), + [clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.concentration_measurement_unit_factory(capabilities.radonMeasurement.NAME), }, [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.ID] = { - [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.tvocMeasurement.NAME, capabilities.tvocMeasurement.tvocLevel, units.PPB), - [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.tvocMeasurement.NAME), - [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.tvocHealthConcern.tvocHealthConcern) - }, - [clusters.ElectricalPowerMeasurement.ID] = { - [clusters.ElectricalPowerMeasurement.attributes.ActivePower.ID] = active_power_handler - }, - [clusters.ElectricalEnergyMeasurement.ID] = { - [clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported.ID] = energy_report_handler_factory(true), - [clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported.ID] = energy_report_handler_factory(false), - }, - [clusters.WaterHeaterMode.ID] = { - [clusters.WaterHeaterMode.attributes.CurrentMode.ID] = water_heater_mode_handler, - [clusters.WaterHeaterMode.attributes.SupportedModes.ID] = water_heater_supported_modes_attr_handler + [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.concentration_level_value_factory(capabilities.tvocHealthConcern.tvocHealthConcern), + [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.concentration_measured_value_factory(capabilities.tvocMeasurement.NAME, capabilities.tvocMeasurement.tvocLevel, fields.units.PPB), + [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.concentration_measurement_unit_factory(capabilities.tvocMeasurement.NAME), }, }, }, - subscribed_attributes = subscribed_attributes, capability_handlers = { - [capabilities.switch.ID] = { - [capabilities.switch.commands.on.NAME] = handle_switch_on, - [capabilities.switch.commands.off.NAME] = handle_switch_off, + [capabilities.airConditionerFanMode.ID] = { + [capabilities.airConditionerFanMode.commands.setFanMode.NAME] = capability_handlers.fan_mode_command_factory(capabilities.airConditionerFanMode) }, - [capabilities.thermostatMode.ID] = { - [capabilities.thermostatMode.commands.setThermostatMode.NAME] = set_thermostat_mode, - [capabilities.thermostatMode.commands.auto.NAME] = thermostat_mode_setter(capabilities.thermostatMode.thermostatMode.auto.NAME), - [capabilities.thermostatMode.commands.off.NAME] = thermostat_mode_setter(capabilities.thermostatMode.thermostatMode.off.NAME), - [capabilities.thermostatMode.commands.cool.NAME] = thermostat_mode_setter(capabilities.thermostatMode.thermostatMode.cool.NAME), - [capabilities.thermostatMode.commands.heat.NAME] = thermostat_mode_setter(capabilities.thermostatMode.thermostatMode.heat.NAME), - [capabilities.thermostatMode.commands.emergencyHeat.NAME] = thermostat_mode_setter(capabilities.thermostatMode.thermostatMode.emergency_heat.NAME) + [capabilities.airPurifierFanMode.ID] = { + [capabilities.airPurifierFanMode.commands.setAirPurifierFanMode.NAME] = capability_handlers.fan_mode_command_factory(capabilities.airPurifierFanMode) }, - [capabilities.thermostatFanMode.ID] = { - [capabilities.thermostatFanMode.commands.setThermostatFanMode.NAME] = set_fan_mode_factory(capabilities.thermostatFanMode), - [capabilities.thermostatFanMode.commands.fanAuto.NAME] = thermostat_fan_mode_setter(capabilities.thermostatFanMode.thermostatFanMode.auto.NAME), - [capabilities.thermostatFanMode.commands.fanOn.NAME] = thermostat_fan_mode_setter(capabilities.thermostatFanMode.thermostatFanMode.on.NAME) + [capabilities.fanMode.ID] = { + [capabilities.fanMode.commands.setFanMode.NAME] = capability_handlers.fan_mode_command_factory(capabilities.fanMode) + }, + [capabilities.fanOscillationMode.ID] = { + [capabilities.fanOscillationMode.commands.setFanOscillationMode.NAME] = capability_handlers.handle_set_fan_oscillation_mode, + }, + [capabilities.fanSpeedPercent.ID] = { + [capabilities.fanSpeedPercent.commands.setPercent.NAME] = capability_handlers.handle_fan_speed_set_percent, + }, + [capabilities.filterState.ID] = { + [capabilities.filterState.commands.resetFilter.NAME] = capability_handlers.handle_filter_state_reset_filter, + }, + [capabilities.mode.ID] = { + [capabilities.mode.commands.setMode.NAME] = capability_handlers.handle_set_mode, + }, + [capabilities.switch.ID] = { + [capabilities.switch.commands.off.NAME] = capability_handlers.handle_switch_off, + [capabilities.switch.commands.on.NAME] = capability_handlers.handle_switch_on, }, [capabilities.thermostatCoolingSetpoint.ID] = { - [capabilities.thermostatCoolingSetpoint.commands.setCoolingSetpoint.NAME] = set_setpoint(clusters.Thermostat.attributes.OccupiedCoolingSetpoint) + [capabilities.thermostatCoolingSetpoint.commands.setCoolingSetpoint.NAME] = capability_handlers.thermostat_set_setpoint_factory(clusters.Thermostat.attributes.OccupiedCoolingSetpoint) }, [capabilities.thermostatHeatingSetpoint.ID] = { - [capabilities.thermostatHeatingSetpoint.commands.setHeatingSetpoint.NAME] = set_setpoint(clusters.Thermostat.attributes.OccupiedHeatingSetpoint) + [capabilities.thermostatHeatingSetpoint.commands.setHeatingSetpoint.NAME] = capability_handlers.thermostat_set_setpoint_factory(clusters.Thermostat.attributes.OccupiedHeatingSetpoint) + }, + [capabilities.thermostatFanMode.ID] = { + [capabilities.thermostatFanMode.commands.fanAuto.NAME] = capability_handlers.thermostat_fan_mode_command_factory(capabilities.thermostatFanMode.thermostatFanMode.auto.NAME), + [capabilities.thermostatFanMode.commands.fanOn.NAME] = capability_handlers.thermostat_fan_mode_command_factory(capabilities.thermostatFanMode.thermostatFanMode.on.NAME), + [capabilities.thermostatFanMode.commands.setThermostatFanMode.NAME] = capability_handlers.fan_mode_command_factory(capabilities.thermostatFanMode), + }, + [capabilities.thermostatMode.ID] = { + [capabilities.thermostatMode.commands.auto.NAME] = capability_handlers.thermostat_mode_command_factory(capabilities.thermostatMode.thermostatMode.auto.NAME), + [capabilities.thermostatMode.commands.cool.NAME] = capability_handlers.thermostat_mode_command_factory(capabilities.thermostatMode.thermostatMode.cool.NAME), + [capabilities.thermostatMode.commands.emergencyHeat.NAME] = capability_handlers.thermostat_mode_command_factory(capabilities.thermostatMode.thermostatMode.emergency_heat.NAME), + [capabilities.thermostatMode.commands.heat.NAME] = capability_handlers.thermostat_mode_command_factory(capabilities.thermostatMode.thermostatMode.heat.NAME), + [capabilities.thermostatMode.commands.off.NAME] = capability_handlers.thermostat_mode_command_factory(capabilities.thermostatMode.thermostatMode.off.NAME), + [capabilities.thermostatMode.commands.setThermostatMode.NAME] = capability_handlers.handle_set_thermostat_mode, + }, + [capabilities.windMode.ID] = { + [capabilities.windMode.commands.setWindMode.NAME] = capability_handlers.handle_set_wind_mode, }, + }, + subscribed_attributes = { [capabilities.airConditionerFanMode.ID] = { - [capabilities.airConditionerFanMode.commands.setFanMode.NAME] = set_fan_mode_factory(capabilities.airConditionerFanMode) + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode }, [capabilities.airPurifierFanMode.ID] = { - [capabilities.airPurifierFanMode.commands.setAirPurifierFanMode.NAME] = set_fan_mode_factory(capabilities.airPurifierFanMode) + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode + }, + [capabilities.battery.ID] = { + clusters.PowerSource.attributes.BatPercentRemaining + }, + [capabilities.batteryLevel.ID] = { + clusters.PowerSource.attributes.BatChargeLevel + }, + [capabilities.energyMeter.ID] = { + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported }, [capabilities.fanMode.ID] = { - [capabilities.fanMode.commands.setFanMode.NAME] = set_fan_mode_factory(capabilities.fanMode) + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode + }, + [capabilities.fanOscillationMode.ID] = { + clusters.FanControl.attributes.RockSupport, + clusters.FanControl.attributes.RockSetting }, [capabilities.fanSpeedPercent.ID] = { - [capabilities.fanSpeedPercent.commands.setPercent.NAME] = set_fan_speed_percent, + clusters.FanControl.attributes.PercentCurrent }, - [capabilities.windMode.ID] = { - [capabilities.windMode.commands.setWindMode.NAME] = set_wind_mode, + [capabilities.filterState.ID] = { + clusters.HepaFilterMonitoring.attributes.Condition, + clusters.ActivatedCarbonFilterMonitoring.attributes.Condition }, - [capabilities.fanOscillationMode.ID] = { - [capabilities.fanOscillationMode.commands.setFanOscillationMode.NAME] = set_rock_mode, + [capabilities.filterStatus.ID] = { + clusters.HepaFilterMonitoring.attributes.ChangeIndication, + clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication }, [capabilities.mode.ID] = { - [capabilities.mode.commands.setMode.NAME] = set_water_heater_mode, + clusters.WaterHeaterMode.attributes.CurrentMode, + clusters.WaterHeaterMode.attributes.SupportedModes + }, + [capabilities.powerConsumptionReport.ID] = { + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported + }, + [capabilities.powerMeter.ID] = { + clusters.ElectricalPowerMeasurement.attributes.ActivePower + }, + [capabilities.relativeHumidityMeasurement.ID] = { + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue + }, + [capabilities.switch.ID] = { + clusters.OnOff.attributes.OnOff + }, + [capabilities.temperatureMeasurement.ID] = { + clusters.Thermostat.attributes.LocalTemperature, + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue + }, + [capabilities.thermostatCoolingSetpoint.ID] = { + clusters.Thermostat.attributes.OccupiedCoolingSetpoint, + clusters.Thermostat.attributes.AbsMinCoolSetpointLimit, + clusters.Thermostat.attributes.AbsMaxCoolSetpointLimit + }, + [capabilities.thermostatFanMode.ID] = { + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode + }, + [capabilities.thermostatHeatingSetpoint.ID] = { + clusters.Thermostat.attributes.OccupiedHeatingSetpoint, + clusters.Thermostat.attributes.AbsMinHeatSetpointLimit, + clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit + }, + [capabilities.thermostatMode.ID] = { + clusters.Thermostat.attributes.SystemMode, + clusters.Thermostat.attributes.ControlSequenceOfOperation + }, + [capabilities.thermostatOperatingState.ID] = { + clusters.Thermostat.attributes.ThermostatRunningState + }, + [capabilities.windMode.ID] = { + clusters.FanControl.attributes.WindSupport, + clusters.FanControl.attributes.WindSetting + }, + -- AIR QUALITY SENSOR DEVICE TYPE SPECIFIC CAPABILITIES -- + [capabilities.airQualityHealthConcern.ID] = { + clusters.AirQuality.attributes.AirQuality + }, + [capabilities.atmosphericPressureMeasurement.ID] = { + clusters.PressureMeasurement.attributes.MeasuredValue + }, + [capabilities.carbonDioxideHealthConcern.ID] = { + clusters.CarbonDioxideConcentrationMeasurement.attributes.LevelValue, + }, + [capabilities.carbonDioxideMeasurement.ID] = { + clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasuredValue, + clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit, + }, + [capabilities.carbonMonoxideHealthConcern.ID] = { + clusters.CarbonMonoxideConcentrationMeasurement.attributes.LevelValue, + }, + [capabilities.carbonMonoxideMeasurement.ID] = { + clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue, + clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit, + }, + [capabilities.dustHealthConcern.ID] = { + clusters.Pm10ConcentrationMeasurement.attributes.LevelValue, + }, + [capabilities.dustSensor.ID] = { + clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue, + clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit, + clusters.Pm10ConcentrationMeasurement.attributes.MeasuredValue, + clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit, + }, + [capabilities.fineDustHealthConcern.ID] = { + clusters.Pm25ConcentrationMeasurement.attributes.LevelValue, + }, + [capabilities.fineDustSensor.ID] = { + clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue, + clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit, + }, + [capabilities.formaldehydeHealthConcern.ID] = { + clusters.FormaldehydeConcentrationMeasurement.attributes.LevelValue, + }, + [capabilities.formaldehydeMeasurement.ID] = { + clusters.FormaldehydeConcentrationMeasurement.attributes.MeasuredValue, + clusters.FormaldehydeConcentrationMeasurement.attributes.MeasurementUnit, + }, + [capabilities.nitrogenDioxideHealthConcern.ID] = { + clusters.NitrogenDioxideConcentrationMeasurement.attributes.LevelValue, + }, + [capabilities.nitrogenDioxideMeasurement.ID] = { + clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasuredValue, + clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasurementUnit + }, + [capabilities.ozoneHealthConcern.ID] = { + clusters.OzoneConcentrationMeasurement.attributes.LevelValue, + }, + [capabilities.ozoneMeasurement.ID] = { + clusters.OzoneConcentrationMeasurement.attributes.MeasuredValue, + clusters.OzoneConcentrationMeasurement.attributes.MeasurementUnit + }, + [capabilities.radonHealthConcern.ID] = { + clusters.RadonConcentrationMeasurement.attributes.LevelValue, + }, + [capabilities.radonMeasurement.ID] = { + clusters.RadonConcentrationMeasurement.attributes.MeasuredValue, + clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit, + }, + [capabilities.relativeHumidityMeasurement.ID] = { + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue + }, + [capabilities.tvocHealthConcern.ID] = { + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue + }, + [capabilities.tvocMeasurement.ID] = { + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasuredValue, + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit, + }, + [capabilities.veryFineDustHealthConcern.ID] = { + clusters.Pm1ConcentrationMeasurement.attributes.LevelValue, + }, + [capabilities.veryFineDustSensor.ID] = { + clusters.Pm1ConcentrationMeasurement.attributes.MeasuredValue, + clusters.Pm1ConcentrationMeasurement.attributes.MeasurementUnit, }, - [capabilities.filterState.ID] = { - [capabilities.filterState.commands.resetFilter.NAME] = reset_filter_state, - } }, supported_capabilities = { - capabilities.thermostatMode, - capabilities.thermostatHeatingSetpoint, - capabilities.thermostatCoolingSetpoint, - capabilities.thermostatFanMode, - capabilities.thermostatOperatingState, capabilities.airConditionerFanMode, - capabilities.fanMode, - capabilities.fanSpeedPercent, + capabilities.airQualityHealthConcern, capabilities.airPurifierFanMode, - capabilities.windMode, - capabilities.fanOscillationMode, capabilities.battery, capabilities.batteryLevel, - capabilities.filterState, - capabilities.filterStatus, - capabilities.airQualityHealthConcern, capabilities.carbonDioxideHealthConcern, capabilities.carbonDioxideMeasurement, capabilities.carbonMonoxideHealthConcern, capabilities.carbonMonoxideMeasurement, + capabilities.dustHealthConcern, + capabilities.dustSensor, + capabilities.energyMeter, + capabilities.fanMode, + capabilities.fanOscillationMode, + capabilities.fanSpeedPercent, + capabilities.filterState, + capabilities.filterStatus, + capabilities.fineDustHealthConcern, + capabilities.fineDustSensor, + capabilities.formaldehydeHealthConcern, + capabilities.formaldehydeMeasurement, + capabilities.mode, capabilities.nitrogenDioxideHealthConcern, capabilities.nitrogenDioxideMeasurement, capabilities.ozoneHealthConcern, capabilities.ozoneMeasurement, - capabilities.formaldehydeHealthConcern, - capabilities.formaldehydeMeasurement, - capabilities.veryFineDustHealthConcern, - capabilities.veryFineDustSensor, - capabilities.fineDustHealthConcern, - capabilities.fineDustSensor, - capabilities.dustSensor, - capabilities.dustHealthConcern, + capabilities.powerConsumptionReport, + capabilities.powerMeter, capabilities.radonHealthConcern, capabilities.radonMeasurement, + capabilities.thermostatCoolingSetpoint, + capabilities.thermostatFanMode, + capabilities.thermostatHeatingSetpoint, + capabilities.thermostatMode, + capabilities.thermostatOperatingState, capabilities.tvocHealthConcern, capabilities.tvocMeasurement, - capabilities.powerMeter, - capabilities.energyMeter, - capabilities.powerConsumptionReport, - capabilities.mode + capabilities.veryFineDustHealthConcern, + capabilities.veryFineDustSensor, + capabilities.windMode, }, } diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier.lua index d9cb8c6309..ca7fddcdd9 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" test.set_rpc_version(0) local capabilities = require "st.capabilities" @@ -20,19 +10,19 @@ local clusters = require "st.matter.clusters" local version = require "version" if version.api < 10 then - clusters.HepaFilterMonitoring = require "HepaFilterMonitoring" - clusters.ActivatedCarbonFilterMonitoring = require "ActivatedCarbonFilterMonitoring" - clusters.AirQuality = require "AirQuality" - clusters.CarbonMonoxideConcentrationMeasurement = require "CarbonMonoxideConcentrationMeasurement" - clusters.CarbonDioxideConcentrationMeasurement = require "CarbonDioxideConcentrationMeasurement" - clusters.FormaldehydeConcentrationMeasurement = require "FormaldehydeConcentrationMeasurement" - clusters.NitrogenDioxideConcentrationMeasurement = require "NitrogenDioxideConcentrationMeasurement" - clusters.OzoneConcentrationMeasurement = require "OzoneConcentrationMeasurement" - clusters.Pm1ConcentrationMeasurement = require "Pm1ConcentrationMeasurement" - clusters.Pm10ConcentrationMeasurement = require "Pm10ConcentrationMeasurement" - clusters.Pm25ConcentrationMeasurement = require "Pm25ConcentrationMeasurement" - clusters.RadonConcentrationMeasurement = require "RadonConcentrationMeasurement" - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "TotalVolatileOrganicCompoundsConcentrationMeasurement" + clusters.HepaFilterMonitoring = require "embedded_clusters.HepaFilterMonitoring" + clusters.ActivatedCarbonFilterMonitoring = require "embedded_clusters.ActivatedCarbonFilterMonitoring" + clusters.AirQuality = require "embedded_clusters.AirQuality" + clusters.CarbonMonoxideConcentrationMeasurement = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement" + clusters.CarbonDioxideConcentrationMeasurement = require "embedded_clusters.CarbonDioxideConcentrationMeasurement" + clusters.FormaldehydeConcentrationMeasurement = require "embedded_clusters.FormaldehydeConcentrationMeasurement" + clusters.NitrogenDioxideConcentrationMeasurement = require "embedded_clusters.NitrogenDioxideConcentrationMeasurement" + clusters.OzoneConcentrationMeasurement = require "embedded_clusters.OzoneConcentrationMeasurement" + clusters.Pm1ConcentrationMeasurement = require "embedded_clusters.Pm1ConcentrationMeasurement" + clusters.Pm10ConcentrationMeasurement = require "embedded_clusters.Pm10ConcentrationMeasurement" + clusters.Pm25ConcentrationMeasurement = require "embedded_clusters.Pm25ConcentrationMeasurement" + clusters.RadonConcentrationMeasurement = require "embedded_clusters.RadonConcentrationMeasurement" + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement" end local mock_device = test.mock_device.build_test_matter_device({ diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_api9.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_api9.lua index 871693d5a6..fd68c958d9 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_api9.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_api9.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" @@ -21,19 +11,19 @@ local version = require "version" version.api = 9 -- include driver-side cluster definitions to test embedded clusters on lower api versions -clusters.HepaFilterMonitoring = require "HepaFilterMonitoring" -clusters.ActivatedCarbonFilterMonitoring = require "ActivatedCarbonFilterMonitoring" -clusters.AirQuality = require "AirQuality" -clusters.CarbonMonoxideConcentrationMeasurement = require "CarbonMonoxideConcentrationMeasurement" -clusters.CarbonDioxideConcentrationMeasurement = require "CarbonDioxideConcentrationMeasurement" -clusters.FormaldehydeConcentrationMeasurement = require "FormaldehydeConcentrationMeasurement" -clusters.NitrogenDioxideConcentrationMeasurement = require "NitrogenDioxideConcentrationMeasurement" -clusters.OzoneConcentrationMeasurement = require "OzoneConcentrationMeasurement" -clusters.Pm1ConcentrationMeasurement = require "Pm1ConcentrationMeasurement" -clusters.Pm10ConcentrationMeasurement = require "Pm10ConcentrationMeasurement" -clusters.Pm25ConcentrationMeasurement = require "Pm25ConcentrationMeasurement" -clusters.RadonConcentrationMeasurement = require "RadonConcentrationMeasurement" -clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "TotalVolatileOrganicCompoundsConcentrationMeasurement" +clusters.HepaFilterMonitoring = require "embedded_clusters.HepaFilterMonitoring" +clusters.ActivatedCarbonFilterMonitoring = require "embedded_clusters.ActivatedCarbonFilterMonitoring" +clusters.AirQuality = require "embedded_clusters.AirQuality" +clusters.CarbonMonoxideConcentrationMeasurement = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement" +clusters.CarbonDioxideConcentrationMeasurement = require "embedded_clusters.CarbonDioxideConcentrationMeasurement" +clusters.FormaldehydeConcentrationMeasurement = require "embedded_clusters.FormaldehydeConcentrationMeasurement" +clusters.NitrogenDioxideConcentrationMeasurement = require "embedded_clusters.NitrogenDioxideConcentrationMeasurement" +clusters.OzoneConcentrationMeasurement = require "embedded_clusters.OzoneConcentrationMeasurement" +clusters.Pm1ConcentrationMeasurement = require "embedded_clusters.Pm1ConcentrationMeasurement" +clusters.Pm10ConcentrationMeasurement = require "embedded_clusters.Pm10ConcentrationMeasurement" +clusters.Pm25ConcentrationMeasurement = require "embedded_clusters.Pm25ConcentrationMeasurement" +clusters.RadonConcentrationMeasurement = require "embedded_clusters.RadonConcentrationMeasurement" +clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement" local mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("air-purifier-hepa-ac-wind.yml"), diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_modular.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_modular.lua index 4f2b5bd0f4..745076cd04 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_modular.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_modular.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" @@ -23,19 +12,19 @@ local version = require "version" test.disable_startup_messages() if version.api < 10 then - clusters.HepaFilterMonitoring = require "HepaFilterMonitoring" - clusters.ActivatedCarbonFilterMonitoring = require "ActivatedCarbonFilterMonitoring" - clusters.AirQuality = require "AirQuality" - clusters.CarbonMonoxideConcentrationMeasurement = require "CarbonMonoxideConcentrationMeasurement" - clusters.CarbonDioxideConcentrationMeasurement = require "CarbonDioxideConcentrationMeasurement" - clusters.FormaldehydeConcentrationMeasurement = require "FormaldehydeConcentrationMeasurement" - clusters.NitrogenDioxideConcentrationMeasurement = require "NitrogenDioxideConcentrationMeasurement" - clusters.OzoneConcentrationMeasurement = require "OzoneConcentrationMeasurement" - clusters.Pm1ConcentrationMeasurement = require "Pm1ConcentrationMeasurement" - clusters.Pm10ConcentrationMeasurement = require "Pm10ConcentrationMeasurement" - clusters.Pm25ConcentrationMeasurement = require "Pm25ConcentrationMeasurement" - clusters.RadonConcentrationMeasurement = require "RadonConcentrationMeasurement" - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "TotalVolatileOrganicCompoundsConcentrationMeasurement" + clusters.HepaFilterMonitoring = require "embedded_clusters.HepaFilterMonitoring" + clusters.ActivatedCarbonFilterMonitoring = require "embedded_clusters.ActivatedCarbonFilterMonitoring" + clusters.AirQuality = require "embedded_clusters.AirQuality" + clusters.CarbonMonoxideConcentrationMeasurement = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement" + clusters.CarbonDioxideConcentrationMeasurement = require "embedded_clusters.CarbonDioxideConcentrationMeasurement" + clusters.FormaldehydeConcentrationMeasurement = require "embedded_clusters.FormaldehydeConcentrationMeasurement" + clusters.NitrogenDioxideConcentrationMeasurement = require "embedded_clusters.NitrogenDioxideConcentrationMeasurement" + clusters.OzoneConcentrationMeasurement = require "embedded_clusters.OzoneConcentrationMeasurement" + clusters.Pm1ConcentrationMeasurement = require "embedded_clusters.Pm1ConcentrationMeasurement" + clusters.Pm10ConcentrationMeasurement = require "embedded_clusters.Pm10ConcentrationMeasurement" + clusters.Pm25ConcentrationMeasurement = require "embedded_clusters.Pm25ConcentrationMeasurement" + clusters.RadonConcentrationMeasurement = require "embedded_clusters.RadonConcentrationMeasurement" + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement" end local mock_device_basic = test.mock_device.build_test_matter_device({ @@ -315,10 +304,6 @@ test.register_coroutine_test( "Test profile change on init for basic Air Purifier device", function() test.socket.device_lifecycle:__queue_receive({ mock_device_basic.id, "doConfigure" }) - test.socket.matter:__queue_receive({ - mock_device_basic.id, - clusters.Thermostat.attributes.AttributeList:build_test_report_data(mock_device_basic, 1, {uint32(0)}) - }) mock_device_basic:expect_metadata_update(expected_update_metadata) mock_device_basic:expect_metadata_update({ provisioning_state = "PROVISIONED" }) diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua index 4a94a9a7e9..8eefceb23b 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_heat_pump.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_heat_pump.lua index b7d4b3a24a..e96bfb69fc 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_heat_pump.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_heat_pump.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local clusters = require "st.matter.clusters" @@ -27,8 +16,8 @@ local HEAT_PUMP_DEVICE_TYPE_ID = 0x0309 local THERMOSTAT_DEVICE_TYPE_ID = 0x0301 if version.api < 11 then - clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" - clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" + clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement" + clusters.ElectricalPowerMeasurement = require "embedded_clusters.ElectricalPowerMeasurement" end local device_desc = { @@ -633,7 +622,7 @@ test.register_message_test( clusters.ElectricalEnergyMeasurement.attributes .CumulativeEnergyImported:build_test_report_data(mock_device, HEAT_PUMP_EP, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 15000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 15000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) } }, { @@ -651,7 +640,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(mock_device, HEAT_PUMP_EP, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 20000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) -- 20Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 20000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) -- 20Wh test.socket.capability:__expect_send( mock_device:generate_test_message("main", @@ -675,7 +664,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(mock_device, HEAT_PUMP_EP, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 30000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) -- 30Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 30000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) -- 30Wh test.socket.capability:__expect_send( mock_device:generate_test_message("main", @@ -698,7 +687,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(mock_device, HEAT_PUMP_EP, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 20000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) -- 20Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 20000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) -- 20Wh test.socket.capability:__expect_send( mock_device:generate_test_message("main", @@ -722,12 +711,12 @@ test.register_coroutine_test( -- do not expect energyMeter event for this report. test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported:build_test_report_data(mock_device, HEAT_PUMP_EP, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 20000, start_timestamp = 0, end_timestamp = 800, start_systime = 0, end_systime = 0 })) }) -- 20Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 20000, start_timestamp = 0, end_timestamp = 800, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) -- 20Wh -- do not expect a powerConsumptionReport to be emitted test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(mock_device, HEAT_PUMP_EP, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 50000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) -- 50Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 50000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) -- 50Wh test.socket.capability:__expect_send( mock_device:generate_test_message("main", diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac.lua index cec0cb9ffc..633fb3f1c1 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac.lua @@ -1,16 +1,6 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" test.set_rpc_version(0) local capabilities = require "st.capabilities" @@ -31,7 +21,7 @@ local mock_device = test.mock_device.build_test_matter_device({ {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, }, device_types = { - device_type_id = 0x0016, device_type_revision = 1, -- RootNode + { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode } }, { @@ -42,6 +32,9 @@ local mock_device = test.mock_device.build_test_matter_device({ {cluster_id = clusters.Thermostat.ID, cluster_type = "SERVER", feature_map = 0}, {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER"}, + }, + device_types = { + { device_type_id = 0x0072, device_type_revision = 1 } -- Room Air Conditioner } } } @@ -163,6 +156,9 @@ local function test_init() end end end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatOperatingState.supportedThermostatOperatingStates({"idle"}, {visibility = {displayed = false}})) + ) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) end @@ -221,6 +217,9 @@ local function test_init_configure() end end end + test.socket.capability:__expect_send( + mock_device_configure:generate_test_message("main", capabilities.thermostatOperatingState.supportedThermostatOperatingStates({"idle", "heating", "cooling"}, {visibility = {displayed = false}})) + ) test.socket.matter:__expect_send({mock_device_configure.id, subscribe_request}) local read_setpoint_deadband = clusters.Thermostat.attributes.MinSetpointDeadBand:read() diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac_modular.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac_modular.lua index 2da9e5c023..be240c95d0 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac_modular.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac_modular.lua @@ -1,16 +1,6 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" @@ -97,6 +87,9 @@ local function initialize_mock_device(generic_mock_device, generic_subscribed_at end end end + test.socket.capability:__expect_send( + generic_mock_device:generate_test_message("main", capabilities.thermostatOperatingState.supportedThermostatOperatingStates({"idle", "heating", "cooling"}, {visibility = {displayed = false}})) + ) test.socket.matter:__expect_send({generic_mock_device.id, subscribe_request}) return subscribe_request end diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_battery.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_battery.lua index 35f312c67e..a28882ca46 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_battery.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_battery.lua @@ -1,20 +1,10 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" +local capabilities = require "st.capabilities" local uint32 = require "st.matter.data_types.Uint32" test.set_rpc_version(7) @@ -80,6 +70,9 @@ local function test_init() end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatOperatingState.supportedThermostatOperatingStates({"idle", "cooling"}, {visibility = {displayed = false}})) + ) test.mock_device.add_test_device(mock_device) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_featuremap.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_featuremap.lua index 37f73d0e50..2db6e20e59 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_featuremap.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_featuremap.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" @@ -20,7 +9,7 @@ local clusters = require "st.matter.clusters" test.set_rpc_version(7) local mock_device = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("thermostat-humidity-fan.yml"), + profile = t_utils.get_profile_definition("thermostat-humidity-fan-nostate.yml"), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -57,7 +46,7 @@ local mock_device = test.mock_device.build_test_matter_device({ }) local mock_device_simple = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("thermostat.yml"), + profile = t_utils.get_profile_definition("thermostat-nostate.yml"), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -92,7 +81,7 @@ local mock_device_simple = test.mock_device.build_test_matter_device({ }) local mock_device_no_battery = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("thermostat.yml"), + profile = t_utils.get_profile_definition("thermostat-nostate.yml"), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -134,7 +123,6 @@ local cluster_subscribe_list = { clusters.Thermostat.attributes.AbsMinHeatSetpointLimit, clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit, clusters.Thermostat.attributes.SystemMode, - clusters.Thermostat.attributes.ThermostatRunningState, clusters.Thermostat.attributes.ControlSequenceOfOperation, clusters.TemperatureMeasurement.attributes.MeasuredValue, clusters.TemperatureMeasurement.attributes.MinMeasuredValue, @@ -153,7 +141,6 @@ local cluster_subscribe_list_simple = { clusters.Thermostat.attributes.AbsMinHeatSetpointLimit, clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit, clusters.Thermostat.attributes.SystemMode, - clusters.Thermostat.attributes.ThermostatRunningState, clusters.Thermostat.attributes.ControlSequenceOfOperation, clusters.TemperatureMeasurement.attributes.MeasuredValue, clusters.TemperatureMeasurement.attributes.MinMeasuredValue, @@ -169,7 +156,6 @@ local cluster_subscribe_list_no_battery = { clusters.Thermostat.attributes.AbsMinHeatSetpointLimit, clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit, clusters.Thermostat.attributes.SystemMode, - clusters.Thermostat.attributes.ThermostatRunningState, clusters.Thermostat.attributes.ControlSequenceOfOperation, clusters.TemperatureMeasurement.attributes.MeasuredValue, clusters.TemperatureMeasurement.attributes.MinMeasuredValue, diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_multiple_device_types.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_multiple_device_types.lua index 7f9577fa8f..90e529beb3 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_multiple_device_types.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_multiple_device_types.lua @@ -1,19 +1,9 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local uint32 = require "st.matter.data_types.Uint32" @@ -159,6 +149,9 @@ local function test_init() test.socket.matter:__expect_send({mock_device.id, read_req}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatOperatingState.supportedThermostatOperatingStates({"idle", "heating", "cooling"}, {visibility = {displayed = false}})) + ) test.socket.matter:__expect_send({mock_device.id, get_subscribe_request(mock_device, cluster_subscribe_list)}) end test.set_test_init_function(test_init) @@ -177,6 +170,9 @@ local function test_init_disorder_endpoints() test.socket.matter:__expect_send({mock_device_disorder_endpoints.id, read_req}) test.socket.device_lifecycle:__queue_receive({ mock_device_disorder_endpoints.id, "init" }) + test.socket.capability:__expect_send( + mock_device_disorder_endpoints:generate_test_message("main", capabilities.thermostatOperatingState.supportedThermostatOperatingStates({"idle", "heating", "cooling"}, {visibility = {displayed = false}})) + ) test.socket.matter:__expect_send({mock_device_disorder_endpoints.id, get_subscribe_request( mock_device_disorder_endpoints, cluster_subscribe_list)}) end @@ -207,6 +203,7 @@ local expected_metadata = { "main", { "relativeHumidityMeasurement", + "fanSpeedPercent", "fanMode", "fanOscillationMode", "thermostatHeatingSetpoint", @@ -231,6 +228,7 @@ local new_cluster_subscribe_list = { clusters.RelativeHumidityMeasurement.attributes.MeasuredValue, clusters.FanControl.attributes.FanMode, clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.PercentCurrent, clusters.FanControl.attributes.RockSupport, -- These two attributes will be subscribed to following the profile clusters.FanControl.attributes.RockSetting, -- change since the fanOscillationMode capability will be enabled. } @@ -252,4 +250,35 @@ test.register_coroutine_test( { test_init = test_init_disorder_endpoints } ) +test.register_coroutine_test( + "PercentCurrent reports and setPercent commands should be handled correctly after profile change", + function() + test_thermostat_device_type_update_modular_profile(mock_device, expected_metadata, + get_subscribe_request(mock_device, new_cluster_subscribe_list)) + + test.wait_for_events() + + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.FanControl.attributes.PercentCurrent:build_test_report_data(mock_device, 2, 10) + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.fanSpeedPercent.percent(10)) + ) + + test.wait_for_events() + + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "fanSpeedPercent", component = "main", command = "setPercent", args = { 50 } } + }) + + test.socket.matter:__expect_send({ + mock_device.id, + clusters.FanControl.attributes.PercentSetting:write(mock_device, 2, 50) + }) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_setpoint_limits.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_setpoint_limits.lua index b28d11dcc7..74e550b344 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_setpoint_limits.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_setpoint_limits.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" @@ -76,6 +65,9 @@ local function test_init() subscribe_request:merge(cluster:subscribe(mock_device)) end end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatOperatingState.supportedThermostatOperatingStates({"idle", "heating", "cooling"}, {visibility = {displayed = false}})) + ) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) local read_setpoint_deadband = clusters.Thermostat.attributes.MinSetpointDeadBand:read() diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_setpoint_limits_rpc.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_setpoint_limits_rpc.lua index 72b46af4f3..13eaa47bfe 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_setpoint_limits_rpc.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermo_setpoint_limits_rpc.lua @@ -1,19 +1,10 @@ --- --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local mock_device = test.mock_device.build_test_matter_device({ @@ -43,6 +34,9 @@ local mock_device = test.mock_device.build_test_matter_device({ }, {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER", feature_map = clusters.PowerSource.types.PowerSourceFeature.BATTERY}, {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "BOTH"}, + }, + device_types = { + { device_type_id = 0x0301, device_type_revision = 1 } -- Thermostat } } } @@ -72,6 +66,9 @@ local function test_init() subscribe_request:merge(cluster:subscribe(mock_device)) end end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatOperatingState.supportedThermostatOperatingStates({"idle", "heating", "cooling"}, {visibility = {displayed = false}})) + ) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) local read_setpoint_deadband = clusters.Thermostat.attributes.MinSetpointDeadBand:read() diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat.lua index 44ed395797..3eed9709a2 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" @@ -31,7 +20,7 @@ local mock_device = test.mock_device.build_test_matter_device({ {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, }, device_types = { - device_type_id = 0x0016, device_type_revision = 1, -- RootNode + { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode } }, { @@ -47,6 +36,9 @@ local mock_device = test.mock_device.build_test_matter_device({ {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER"}, + }, + device_types = { + { device_type_id = 0x0301, device_type_revision = 1 } -- Thermostat } } } @@ -81,6 +73,9 @@ local mock_device_auto = test.mock_device.build_test_matter_device({ {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER"}, + }, + device_types = { + { device_type_id = 0x0301, device_type_revision = 1 } -- Thermostat } } } @@ -114,7 +109,10 @@ local function test_init() end end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - test.mock_device.add_test_device(mock_device) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatOperatingState.supportedThermostatOperatingStates({"idle", "heating", "cooling"}, {visibility = {displayed = false}})) + ) + test.mock_device.add_test_device(mock_device) end test.set_test_init_function(test_init) @@ -146,6 +144,9 @@ local function test_init_auto() end end test.socket.matter:__expect_send({mock_device_auto.id, subscribe_request}) + test.socket.capability:__expect_send( + mock_device_auto:generate_test_message("main", capabilities.thermostatOperatingState.supportedThermostatOperatingStates({"idle", "heating", "cooling"}, {visibility = {displayed = false}})) + ) test.socket.matter:__expect_send({mock_device_auto.id, clusters.Thermostat.attributes.MinSetpointDeadBand:read(mock_device_auto)}) test.mock_device.add_test_device(mock_device_auto) end diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_composed_bridged.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_composed_bridged.lua index e901ac59a4..ef85b147af 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_composed_bridged.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_composed_bridged.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" @@ -46,6 +35,9 @@ local mock_device = test.mock_device.build_test_matter_device({ { cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER" }, { cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER" }, { cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0301, device_type_revision = 1 } -- Thermostat } } } @@ -78,6 +70,9 @@ local function test_init() subscribe_request:merge(cluster:subscribe(mock_device)) end end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatOperatingState.supportedThermostatOperatingStates({"idle", "heating", "cooling"}, {visibility = {displayed = false}})) + ) test.socket.matter:__expect_send({ mock_device.id, subscribe_request }) test.mock_device.add_test_device(mock_device) end diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_modular.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_modular.lua index 6645d39692..bf985671cf 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_modular.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_modular.lua @@ -1,18 +1,8 @@ --- Copyright 2025 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" +local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" local im = require "st.matter.interaction_model" @@ -106,6 +96,9 @@ local function test_init() test.socket.matter:__expect_send({ mock_device_basic.id, read_request }) test.socket.device_lifecycle:__queue_receive({ mock_device_basic.id, "init" }) + test.socket.capability:__expect_send( + mock_device_basic:generate_test_message("main", capabilities.thermostatOperatingState.supportedThermostatOperatingStates({"idle", "heating", "cooling"}, {visibility = {displayed = false}})) + ) subscribe_request_basic = initialize_mock_device(mock_device_basic, subscribed_attributes) end diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_rpc5.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_rpc5.lua index b52b08027e..a9bbf59930 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_rpc5.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_rpc5.lua @@ -1,18 +1,8 @@ --- Copyright 2022 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.matter.clusters" +local capabilities = require "st.capabilities" local test = require "integration_test" local t_utils = require "integration_test.utils" local utils = require "st.utils" @@ -50,6 +40,9 @@ local mock_device = test.mock_device.build_test_matter_device({ {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER"}, + }, + device_types = { + { device_type_id = 0x0301, device_type_revision = 1 } -- Thermostat } } } @@ -81,6 +74,9 @@ local function test_init() subscribe_request:merge(cluster:subscribe(mock_device)) end end + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatOperatingState.supportedThermostatOperatingStates({"idle", "heating", "cooling"}, {visibility = {displayed = false}})) + ) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) end diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_water_heater.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_water_heater.lua index 802f323181..b39db4136b 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_water_heater.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_water_heater.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" @@ -19,11 +8,12 @@ local version = require "version" local clusters = require "st.matter.clusters" if version.api < 13 then - clusters.WaterHeaterMode = require "WaterHeaterMode" + clusters.WaterHeaterMode = require "embedded_clusters.WaterHeaterMode" end if version.api < 11 then - clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" + clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement" + clusters.ElectricalPowerMeasurement = require "embedded_clusters.ElectricalPowerMeasurement" end local WATER_HEATER_EP = 10 @@ -249,7 +239,7 @@ test.register_message_test( clusters.ElectricalEnergyMeasurement.attributes .CumulativeEnergyImported:build_test_report_data(mock_device, ELECTRICAL_SENSOR_EP, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 15000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 15000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) } }, { @@ -267,7 +257,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(mock_device, ELECTRICAL_SENSOR_EP, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 20000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) -- 20Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 20000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) -- 20Wh test.socket.capability:__expect_send( mock_device:generate_test_message("main", @@ -288,7 +278,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(mock_device, ELECTRICAL_SENSOR_EP, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 30000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) -- 30Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 30000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) -- 30Wh test.socket.capability:__expect_send( mock_device:generate_test_message("main", @@ -303,7 +293,7 @@ test.register_coroutine_test( test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(mock_device, ELECTRICAL_SENSOR_EP, - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 50000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) -- 30Wh + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 50000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0, apparent_energy = 0, reactive_energy = 0 })) }) -- 30Wh test.socket.capability:__expect_send( mock_device:generate_test_message("main", diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua new file mode 100644 index 0000000000..78f5ee3bf2 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua @@ -0,0 +1,661 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local log = require "log" +local version = require "version" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local st_utils = require "st.utils" +local fields = require "thermostat_utils.fields" +local thermostat_utils = require "thermostat_utils.utils" + +if version.api < 10 then + clusters.HepaFilterMonitoring = require "embedded_clusters.HepaFilterMonitoring" + clusters.ActivatedCarbonFilterMonitoring = require "embedded_clusters.ActivatedCarbonFilterMonitoring" +end + +if version.api < 11 then + clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement" +end + +if version.api < 13 then + clusters.WaterHeaterMode = require "embedded_clusters.WaterHeaterMode" +end + +local AttributeHandlers = {} + + +-- [[ THERMOSTAT CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.thermostat_attribute_list_handler(driver, device, ib, response) + local device_cfg = require "thermostat_utils.device_configuration" + for _, attr in ipairs(ib.data.elements) do + -- mark whether the optional attribute ThermostatRunningState (0x029) is present and try profiling + if attr.value == 0x029 then + device:set_field(fields.profiling_data.THERMOSTAT_RUNNING_STATE_SUPPORT, true) + device_cfg.match_profile(device) + return + end + end + device:set_field(fields.profiling_data.THERMOSTAT_RUNNING_STATE_SUPPORT, false) + device_cfg.match_profile(device) +end + +function AttributeHandlers.system_mode_handler(driver, device, ib, response) + if device:get_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN) == nil then -- this being nil means the control_sequence_of_operation_handler hasn't run. + device.log.info_with({hub_logs = true}, "In the SystemMode handler: ControlSequenceOfOperation has not run yet. Exiting early.") + device:set_field(fields.SAVED_SYSTEM_MODE_IB, ib) + return + end + + local supported_modes = device:get_latest_state(device:endpoint_to_component(ib.endpoint_id), capabilities.thermostatMode.ID, capabilities.thermostatMode.supportedThermostatModes.NAME) or {} + -- check that the given mode was in the supported modes list + if thermostat_utils.tbl_contains(supported_modes, fields.THERMOSTAT_MODE_MAP[ib.data.value].NAME) then + device:emit_event_for_endpoint(ib.endpoint_id, fields.THERMOSTAT_MODE_MAP[ib.data.value]()) + return + end + -- if the value is not found in the supported modes list, check if it's disallowed and early return if so. + local disallowed_thermostat_modes = device:get_field(fields.DISALLOWED_THERMOSTAT_MODES) or {} + if thermostat_utils.tbl_contains(disallowed_thermostat_modes, fields.THERMOSTAT_MODE_MAP[ib.data.value].NAME) then + return + end + -- if we get here, then the reported mode is allowed and not in our mode map + -- add the mode to the OPTIONAL_THERMOSTAT_MODES_SEEN and supportedThermostatModes tables + local optional_modes_seen = st_utils.deep_copy(device:get_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN)) or {} + table.insert(optional_modes_seen, fields.THERMOSTAT_MODE_MAP[ib.data.value].NAME) + device:set_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN, optional_modes_seen, {persist=true}) + local sm_copy = st_utils.deep_copy(supported_modes) + table.insert(sm_copy, fields.THERMOSTAT_MODE_MAP[ib.data.value].NAME) + local supported_modes_event = capabilities.thermostatMode.supportedThermostatModes(sm_copy, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, supported_modes_event) + device:emit_event_for_endpoint(ib.endpoint_id, fields.THERMOSTAT_MODE_MAP[ib.data.value]()) +end + +function AttributeHandlers.thermostat_running_state_handler(driver, device, ib, response) + for mode, operating_state in pairs(fields.THERMOSTAT_OPERATING_MODE_MAP) do + if ((ib.data.value >> mode) & 1) > 0 then + device:emit_event_for_endpoint(ib.endpoint_id, operating_state()) + return + end + end + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.thermostatOperatingState.thermostatOperatingState.idle()) +end + +function AttributeHandlers.control_sequence_of_operation_handler(driver, device, ib, response) + -- The ControlSequenceOfOperation attribute only directly specifies what can't be operated by the operating environment, not what can. + -- However, we assert here that a Cooling enum value implies that SystemMode supports cooling, and the same for a Heating enum. + -- We also assert that Off is supported, though per spec this is optional. + if device:get_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN) == nil then + device:set_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN, {capabilities.thermostatMode.thermostatMode.off.NAME}, {persist=true}) + end + local supported_modes = st_utils.deep_copy(device:get_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN)) + local disallowed_mode_operations = {} + + local modes_for_inclusion = {} + if ib.data.value <= clusters.Thermostat.attributes.ControlSequenceOfOperation.COOLING_WITH_REHEAT then + local _, found_idx = thermostat_utils.tbl_contains(supported_modes, capabilities.thermostatMode.thermostatMode.emergency_heat.NAME) + if found_idx then + table.remove(supported_modes, found_idx) -- if seen before, remove now + end + table.insert(supported_modes, capabilities.thermostatMode.thermostatMode.cool.NAME) + table.insert(disallowed_mode_operations, capabilities.thermostatMode.thermostatMode.heat.NAME) + table.insert(disallowed_mode_operations, capabilities.thermostatMode.thermostatMode.emergency_heat.NAME) + elseif ib.data.value <= clusters.Thermostat.attributes.ControlSequenceOfOperation.HEATING_WITH_REHEAT then + local _, found_idx = thermostat_utils.tbl_contains(supported_modes, capabilities.thermostatMode.thermostatMode.precooling.NAME) + if found_idx then + table.remove(supported_modes, found_idx) -- if seen before, remove now + end + table.insert(supported_modes, capabilities.thermostatMode.thermostatMode.heat.NAME) + table.insert(disallowed_mode_operations, capabilities.thermostatMode.thermostatMode.cool.NAME) + table.insert(disallowed_mode_operations, capabilities.thermostatMode.thermostatMode.precooling.NAME) + elseif ib.data.value <= clusters.Thermostat.attributes.ControlSequenceOfOperation.COOLING_AND_HEATING_WITH_REHEAT then + table.insert(modes_for_inclusion, capabilities.thermostatMode.thermostatMode.cool.NAME) + table.insert(modes_for_inclusion, capabilities.thermostatMode.thermostatMode.heat.NAME) + end + + -- check whether the Auto Mode should be supported in SystemMode, though this is unrelated to ControlSequenceOfOperation + local auto = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.AUTOMODE}) + if #auto > 0 then + table.insert(modes_for_inclusion, capabilities.thermostatMode.thermostatMode.auto.NAME) + else + table.insert(disallowed_mode_operations, capabilities.thermostatMode.thermostatMode.auto.NAME) + end + + -- if a disallowed value was once allowed and added, it should be removed now. + for index, mode in pairs(supported_modes) do + if thermostat_utils.tbl_contains(disallowed_mode_operations, mode) then + table.remove(supported_modes, index) + end + end + -- do not include any values twice + for _, mode in pairs(modes_for_inclusion) do + if not thermostat_utils.tbl_contains(supported_modes, mode) then + table.insert(supported_modes, mode) + end + end + device:set_field(fields.DISALLOWED_THERMOSTAT_MODES, disallowed_mode_operations) + local event = capabilities.thermostatMode.supportedThermostatModes(supported_modes, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, event) + + -- will be set by the SystemMode handler if this handler hasn't run yet. + if device:get_field(fields.SAVED_SYSTEM_MODE_IB) then + AttributeHandlers.system_mode_handler(driver, device, device:get_field(fields.SAVED_SYSTEM_MODE_IB), response) + device:set_field(fields.SAVED_SYSTEM_MODE_IB, nil) + end +end + +function AttributeHandlers.min_setpoint_deadband_handler(driver, device, ib, response) + local val = ib.data.value / 10.0 + log.info("Setting " .. fields.setpoint_limit_device_field.MIN_DEADBAND .. " to " .. string.format("%s", val)) + device:set_field(fields.setpoint_limit_device_field.MIN_DEADBAND, val, { persist = true }) + device:set_field(fields.setpoint_limit_device_field.MIN_SETPOINT_DEADBAND_CHECKED, true, {persist = true}) +end + +function AttributeHandlers.abs_heat_setpoint_limit_factory(minOrMax) + return function(driver, device, ib, response) + if ib.data.value == nil then + return + end + local MAX_TEMP_IN_C = fields.THERMOSTAT_MAX_TEMP_IN_C + local MIN_TEMP_IN_C = fields.THERMOSTAT_MIN_TEMP_IN_C + local is_water_heater_device = (thermostat_utils.get_device_type(device) == fields.WATER_HEATER_DEVICE_TYPE_ID) + if is_water_heater_device then + MAX_TEMP_IN_C = fields.WATER_HEATER_MAX_TEMP_IN_C + MIN_TEMP_IN_C = fields.WATER_HEATER_MIN_TEMP_IN_C + end + local val = ib.data.value / 100.0 + val = st_utils.clamp_value(val, MIN_TEMP_IN_C, MAX_TEMP_IN_C) + device:set_field(minOrMax, val) + local min = device:get_field(fields.setpoint_limit_device_field.MIN_HEAT) + local max = device:get_field(fields.setpoint_limit_device_field.MAX_HEAT) + if min ~= nil and max ~= nil then + if min < max then + -- Only emit the capability for RPC version >= 5 (unit conversion for + -- heating setpoint range capability is only supported for RPC >= 5) + if version.rpc >= 5 then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.thermostatHeatingSetpoint.heatingSetpointRange({ value = { minimum = min, maximum = max, step = 0.1 }, unit = "C" })) + end + else + device.log.warn_with({hub_logs = true}, string.format("Device reported a min heating setpoint %d that is not lower than the reported max %d", min, max)) + end + end + end +end + +function AttributeHandlers.abs_cool_setpoint_limit_factory(minOrMax) + return function(driver, device, ib, response) + if ib.data.value == nil then + return + end + local val = ib.data.value / 100.0 + val = st_utils.clamp_value(val, fields.THERMOSTAT_MIN_TEMP_IN_C, fields.THERMOSTAT_MAX_TEMP_IN_C) + device:set_field(minOrMax, val) + local min = device:get_field(fields.setpoint_limit_device_field.MIN_COOL) + local max = device:get_field(fields.setpoint_limit_device_field.MAX_COOL) + if min ~= nil and max ~= nil then + if min < max then + -- Only emit the capability for RPC version >= 5 (unit conversion for + -- cooling setpoint range capability is only supported for RPC >= 5) + if version.rpc >= 5 then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.thermostatCoolingSetpoint.coolingSetpointRange({ value = { minimum = min, maximum = max, step = 0.1 }, unit = "C" })) + end + else + device.log.warn_with({hub_logs = true}, string.format("Device reported a min cooling setpoint %d that is not lower than the reported max %d", min, max)) + end + end + end +end + + +-- [[ TEMPERATURE MEASUREMENT CLUSER ATTRIBUTES ]] -- + +function AttributeHandlers.temperature_handler_factory(attribute) + return function(driver, device, ib, response) + if ib.data.value == nil then + return + end + local unit = "C" + + -- Only emit the capability for RPC version >= 5, since unit conversion for + -- range capabilities is only supported in that case. + if version.rpc >= 5 then + local event + if attribute == capabilities.thermostatCoolingSetpoint.coolingSetpoint then + local range = { + minimum = device:get_field(fields.setpoint_limit_device_field.MIN_COOL) or fields.THERMOSTAT_MIN_TEMP_IN_C, + maximum = device:get_field(fields.setpoint_limit_device_field.MAX_COOL) or fields.THERMOSTAT_MAX_TEMP_IN_C, + step = 0.1 + } + event = capabilities.thermostatCoolingSetpoint.coolingSetpointRange({value = range, unit = unit}) + device:emit_event_for_endpoint(ib.endpoint_id, event) + elseif attribute == capabilities.thermostatHeatingSetpoint.heatingSetpoint then + local MAX_TEMP_IN_C = fields.THERMOSTAT_MAX_TEMP_IN_C + local MIN_TEMP_IN_C = fields.THERMOSTAT_MIN_TEMP_IN_C + local is_water_heater_device = thermostat_utils.get_device_type(device) == fields.WATER_HEATER_DEVICE_TYPE_ID + if is_water_heater_device then + MAX_TEMP_IN_C = fields.WATER_HEATER_MAX_TEMP_IN_C + MIN_TEMP_IN_C = fields.WATER_HEATER_MIN_TEMP_IN_C + end + + local range = { + minimum = device:get_field(fields.setpoint_limit_device_field.MIN_HEAT) or MIN_TEMP_IN_C, + maximum = device:get_field(fields.setpoint_limit_device_field.MAX_HEAT) or MAX_TEMP_IN_C, + step = 0.1 + } + event = capabilities.thermostatHeatingSetpoint.heatingSetpointRange({value = range, unit = unit}) + device:emit_event_for_endpoint(ib.endpoint_id, event) + end + end + + local temp = ib.data.value / 100.0 + device:emit_event_for_endpoint(ib.endpoint_id, attribute({value = temp, unit = unit})) + end +end + +function AttributeHandlers.temperature_measured_value_bounds_factory(minOrMax) + return function(driver, device, ib, response) + if ib.data.value == nil then + return + end + local temp = ib.data.value / 100.0 + local unit = "C" + temp = st_utils.clamp_value(temp, fields.THERMOSTAT_MIN_TEMP_IN_C, fields.THERMOSTAT_MAX_TEMP_IN_C) + thermostat_utils.set_field_for_endpoint(device, minOrMax, ib.endpoint_id, temp) + local min = thermostat_utils.get_field_for_endpoint(device, fields.setpoint_limit_device_field.MIN_TEMP, ib.endpoint_id) + local max = thermostat_utils.get_field_for_endpoint(device, fields.setpoint_limit_device_field.MAX_TEMP, ib.endpoint_id) + if min ~= nil and max ~= nil then + if min < max then + -- Only emit the capability for RPC version >= 5 (unit conversion for + -- temperature range capability is only supported for RPC >= 5) + if version.rpc >= 5 then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = min, maximum = max }, unit = unit })) + end + thermostat_utils.set_field_for_endpoint(device, fields.setpoint_limit_device_field.MIN_TEMP, ib.endpoint_id, nil) + thermostat_utils.set_field_for_endpoint(device, fields.setpoint_limit_device_field.MAX_TEMP, ib.endpoint_id, nil) + else + device.log.warn_with({hub_logs = true}, string.format("Device reported a min temperature %d that is not lower than the reported max temperature %d", min, max)) + end + end + end +end + + +--[[ FAN CONTROL CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.fan_mode_handler(driver, device, ib, response) + local fan_mode_event = { + [clusters.FanControl.attributes.FanMode.OFF] = { capabilities.fanMode.fanMode.off(), + capabilities.airConditionerFanMode.fanMode("off"), + capabilities.airPurifierFanMode.airPurifierFanMode.off(), + nil }, -- 'OFF' is not supported by thermostatFanMode + [clusters.FanControl.attributes.FanMode.LOW] = { capabilities.fanMode.fanMode.low(), + capabilities.airConditionerFanMode.fanMode("low"), + capabilities.airPurifierFanMode.airPurifierFanMode.low(), + capabilities.thermostatFanMode.thermostatFanMode.on() }, + [clusters.FanControl.attributes.FanMode.MEDIUM] = { capabilities.fanMode.fanMode.medium(), + capabilities.airConditionerFanMode.fanMode("medium"), + capabilities.airPurifierFanMode.airPurifierFanMode.medium(), + capabilities.thermostatFanMode.thermostatFanMode.on() }, + [clusters.FanControl.attributes.FanMode.HIGH] = { capabilities.fanMode.fanMode.high(), + capabilities.airConditionerFanMode.fanMode("high"), + capabilities.airPurifierFanMode.airPurifierFanMode.high(), + capabilities.thermostatFanMode.thermostatFanMode.on() }, + [clusters.FanControl.attributes.FanMode.ON] = { capabilities.fanMode.fanMode.auto(), + capabilities.airConditionerFanMode.fanMode("auto"), + capabilities.airPurifierFanMode.airPurifierFanMode.auto(), + capabilities.thermostatFanMode.thermostatFanMode.on() }, + [clusters.FanControl.attributes.FanMode.AUTO] = { capabilities.fanMode.fanMode.auto(), + capabilities.airConditionerFanMode.fanMode("auto"), + capabilities.airPurifierFanMode.airPurifierFanMode.auto(), + capabilities.thermostatFanMode.thermostatFanMode.auto() }, + [clusters.FanControl.attributes.FanMode.SMART] = { capabilities.fanMode.fanMode.auto(), + capabilities.airConditionerFanMode.fanMode("auto"), + capabilities.airPurifierFanMode.airPurifierFanMode.auto(), + capabilities.thermostatFanMode.thermostatFanMode.auto() } + } + local fan_mode_idx = device:supports_capability_by_id(capabilities.fanMode.ID) and 1 or + device:supports_capability_by_id(capabilities.airConditionerFanMode.ID) and 2 or + device:supports_capability_by_id(capabilities.airPurifierFanMode.ID) and 3 or + device:supports_capability_by_id(capabilities.thermostatFanMode.ID) and 4 + if fan_mode_idx ~= false and fan_mode_event[ib.data.value][fan_mode_idx] then + device:emit_event_for_endpoint(ib.endpoint_id, fan_mode_event[ib.data.value][fan_mode_idx]) + else + log.warn(string.format("Invalid Fan Mode (%s)", ib.data.value)) + end +end + +function AttributeHandlers.fan_mode_sequence_handler(driver, device, ib, response) + local supportedFanModes, supported_fan_modes_attribute + if ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH then + supportedFanModes = { "off", "low", "medium", "high" } + elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH then + supportedFanModes = { "off", "low", "high" } + elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH_AUTO then + supportedFanModes = { "off", "low", "medium", "high", "auto" } + elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH_AUTO then + supportedFanModes = { "off", "low", "high", "auto" } + elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_HIGH_AUTO then + supportedFanModes = { "off", "high", "auto" } + else + supportedFanModes = { "off", "high" } + end + + if device:supports_capability_by_id(capabilities.airPurifierFanMode.ID) then + supported_fan_modes_attribute = capabilities.airPurifierFanMode.supportedAirPurifierFanModes + elseif device:supports_capability_by_id(capabilities.airConditionerFanMode.ID) then + supported_fan_modes_attribute = capabilities.airConditionerFanMode.supportedAcFanModes + elseif device:supports_capability_by_id(capabilities.thermostatFanMode.ID) then + supported_fan_modes_attribute = capabilities.thermostatFanMode.supportedThermostatFanModes + -- Our thermostat fan mode control is not granular enough to handle all of the supported modes + if ib.data.value >= clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH_AUTO and + ib.data.value <= clusters.FanControl.attributes.FanModeSequence.OFF_ON_AUTO then + supportedFanModes = { "auto", "on" } + else + supportedFanModes = { "on" } + end + else + supported_fan_modes_attribute = capabilities.fanMode.supportedFanModes + end + + local event = supported_fan_modes_attribute(supportedFanModes, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, event) +end + +function AttributeHandlers.percent_current_handler(driver, device, ib, response) + local speed = 0 + if ib.data.value ~= nil then + speed = st_utils.clamp_value(ib.data.value, fields.MIN_ALLOWED_PERCENT_VALUE, fields.MAX_ALLOWED_PERCENT_VALUE) + end + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanSpeedPercent.percent(speed)) +end + +function AttributeHandlers.wind_support_handler(driver, device, ib, response) + local supported_wind_modes = {capabilities.windMode.windMode.noWind.NAME} + for mode, wind_mode in pairs(fields.WIND_MODE_MAP) do + if ((ib.data.value >> mode) & 1) > 0 then + table.insert(supported_wind_modes, wind_mode.NAME) + end + end + local event = capabilities.windMode.supportedWindModes(supported_wind_modes, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, event) +end + +function AttributeHandlers.wind_setting_handler(driver, device, ib, response) + for index, wind_mode in pairs(fields.WIND_MODE_MAP) do + if ((ib.data.value >> index) & 1) > 0 then + device:emit_event_for_endpoint(ib.endpoint_id, wind_mode()) + return + end + end + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.windMode.windMode.noWind()) +end + +function AttributeHandlers.rock_support_handler(driver, device, ib, response) + local supported_rock_modes = {capabilities.fanOscillationMode.fanOscillationMode.off.NAME} + for mode, rock_mode in pairs(fields.ROCK_MODE_MAP) do + if ((ib.data.value >> mode) & 1) > 0 then + table.insert(supported_rock_modes, rock_mode.NAME) + end + end + local event = capabilities.fanOscillationMode.supportedFanOscillationModes(supported_rock_modes, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, event) +end + +function AttributeHandlers.rock_setting_handler(driver, device, ib, response) + for index, rock_mode in pairs(fields.ROCK_MODE_MAP) do + if ((ib.data.value >> index) & 1) > 0 then + device:emit_event_for_endpoint(ib.endpoint_id, rock_mode()) + return + end + end + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanOscillationMode.fanOscillationMode.off()) +end + + +-- [[ HEPA FILTER MONITORING CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.hepa_filter_condition_handler(driver, device, ib, response) + local component = device.profile.components["hepaFilter"] + local condition = ib.data.value + device:emit_component_event(component, capabilities.filterState.filterLifeRemaining(condition)) +end + +function AttributeHandlers.hepa_filter_change_indication_handler(driver, device, ib, response) + local component = device.profile.components["hepaFilter"] + if ib.data.value == clusters.HepaFilterMonitoring.attributes.ChangeIndication.OK then + device:emit_component_event(component, capabilities.filterStatus.filterStatus.normal()) + elseif ib.data.value == clusters.HepaFilterMonitoring.attributes.ChangeIndication.WARNING then + device:emit_component_event(component, capabilities.filterStatus.filterStatus.normal()) + elseif ib.data.value == clusters.HepaFilterMonitoring.attributes.ChangeIndication.CRITICAL then + device:emit_component_event(component, capabilities.filterStatus.filterStatus.replace()) + end +end + + +-- [[ ACTIVATED CARBON FILTER MONITORING CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.activated_carbon_filter_condition_handler(driver, device, ib, response) + local component = device.profile.components["activatedCarbonFilter"] + local condition = ib.data.value + device:emit_component_event(component, capabilities.filterState.filterLifeRemaining(condition)) +end + +function AttributeHandlers.activated_carbon_filter_change_indication_handler(driver, device, ib, response) + local component = device.profile.components["activatedCarbonFilter"] + if ib.data.value == clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.OK then + device:emit_component_event(component, capabilities.filterStatus.filterStatus.normal()) + elseif ib.data.value == clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.WARNING then + device:emit_component_event(component, capabilities.filterStatus.filterStatus.normal()) + elseif ib.data.value == clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.CRITICAL then + device:emit_component_event(component, capabilities.filterStatus.filterStatus.replace()) + end +end + + +--[[ AIR QUALITY SENSOR CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.air_quality_handler(driver, device, ib, response) + local state = ib.data.value + if state == 0 then -- Unknown + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.unknown()) + elseif state == 1 then -- Good + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.good()) + elseif state == 2 then -- Fair + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.moderate()) + elseif state == 3 then -- Moderate + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.slightlyUnhealthy()) + elseif state == 4 then -- Poor + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.unhealthy()) + elseif state == 5 then -- VeryPoor + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.veryUnhealthy()) + elseif state == 6 then -- ExtremelyPoor + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.hazardous()) + end +end + + +-- [[ CONCENTRATION CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.concentration_measurement_unit_factory(capability_name) + return function(driver, device, ib, response) + device:set_field(capability_name.."_unit", ib.data.value, {persist = true}) + end +end + +function AttributeHandlers.concentration_level_value_factory(attribute) + return function(driver, device, ib, response) + device:emit_event_for_endpoint(ib.endpoint_id, attribute(fields.level_strings[ib.data.value])) + end +end + +function AttributeHandlers.concentration_measured_value_factory(capability_name, attribute, target_unit) + return function(driver, device, ib, response) + local reporting_unit = device:get_field(capability_name.."_unit") + + if not reporting_unit then + reporting_unit = fields.unit_default[capability_name] + device:set_field(capability_name.."_unit", reporting_unit, {persist = true}) + end + + local value = nil + if reporting_unit then + value = thermostat_utils.unit_conversion(ib.data.value, reporting_unit, target_unit, capability_name) + end + + if value then + device:emit_event_for_endpoint(ib.endpoint_id, attribute({value = value, unit = fields.unit_strings[target_unit]})) + -- handle case where device profile supports both fineDustLevel and dustLevel + if capability_name == capabilities.fineDustSensor.NAME and device:supports_capability(capabilities.dustSensor) then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.dustSensor.fineDustLevel({value = value, unit = fields.unit_strings[target_unit]})) + end + end + end +end + + +-- [[ POWER SOURCE CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.bat_percent_remaining_handler(driver, device, ib, response) + if ib.data.value then + device:emit_event(capabilities.battery.battery(math.floor(ib.data.value / 2.0 + 0.5))) + end +end + +function AttributeHandlers.bat_charge_level_handler(driver, device, ib, response) + if ib.data.value == clusters.PowerSource.types.BatChargeLevelEnum.OK then + device:emit_event(capabilities.batteryLevel.battery.normal()) + elseif ib.data.value == clusters.PowerSource.types.BatChargeLevelEnum.WARNING then + device:emit_event(capabilities.batteryLevel.battery.warning()) + elseif ib.data.value == clusters.PowerSource.types.BatChargeLevelEnum.CRITICAL then + device:emit_event(capabilities.batteryLevel.battery.critical()) + end +end + +function AttributeHandlers.power_source_attribute_list_handler(driver, device, ib, response) + local device_cfg = require "thermostat_utils.device_configuration" + for _, attr in ipairs(ib.data.elements) do + -- mark if the device if BatPercentRemaining (Attribute ID 0x0C) or + -- BatChargeLevel (Attribute ID 0x0E) is present and try profiling. + if attr.value == 0x0C then + device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.BATTERY_PERCENTAGE) + device_cfg.match_profile(device) + return + elseif attr.value == 0x0E then + device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.BATTERY_LEVEL) + device_cfg.match_profile(device) + return + end + end +end + + +-- [[ ELECTRICAL POWER MEASUREMENT CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.active_power_handler(driver, device, ib, response) + if ib.data.value then + local watt_value = ib.data.value / 1000 + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.powerMeter.power({ value = watt_value, unit = "W" })) + if type(device.register_native_capability_attr_handler) == "function" then + device:register_native_capability_attr_handler("powerMeter","power") + end + end +end + + +-- [[ ELECTRICAL ENERGY MEASUREMENT CLUSTER ATTRIBUTES ]] -- + +local function periodic_energy_imported_handler(driver, device, ib, response) + if ib.data then + if version.api < 11 then + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:augment_type(ib.data) + end + local endpoint_id = string.format(ib.endpoint_id) + local energy_imported_Wh = st_utils.round(ib.data.elements.energy.value / 1000) --convert mWh to Wh + local cumulative_energy_imported = device:get_field(fields.TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP) or {} + cumulative_energy_imported[endpoint_id] = cumulative_energy_imported[endpoint_id] or 0 + cumulative_energy_imported[endpoint_id] = cumulative_energy_imported[endpoint_id] + energy_imported_Wh + device:set_field(fields.TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP, cumulative_energy_imported, { persist = true }) + local total_cumulative_energy_imported = thermostat_utils.get_total_cumulative_energy_imported(device) + device:emit_component_event(device.profile.components["main"], ib.endpoint_id, capabilities.energyMeter.energy({value = total_cumulative_energy_imported, unit = "Wh"})) + thermostat_utils.report_power_consumption_to_st_energy(device, total_cumulative_energy_imported) + end +end + +local function cumulative_energy_imported_handler(driver, device, ib, response) + if ib.data then + if version.api < 11 then + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:augment_type(ib.data) + end + local endpoint_id = string.format(ib.endpoint_id) + local cumulative_energy_imported = device:get_field(fields.TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP) or {} + local cumulative_energy_imported_Wh = st_utils.round( ib.data.elements.energy.value / 1000) -- convert mWh to Wh + cumulative_energy_imported[endpoint_id] = cumulative_energy_imported_Wh + device:set_field(fields.TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP, cumulative_energy_imported, { persist = true }) + local total_cumulative_energy_imported = thermostat_utils.get_total_cumulative_energy_imported(device) + device:emit_component_event(device.profile.components["main"], capabilities.energyMeter.energy({ value = total_cumulative_energy_imported, unit = "Wh" })) + thermostat_utils.report_power_consumption_to_st_energy(device, total_cumulative_energy_imported) + end +end + +function AttributeHandlers.energy_imported_factory(is_cumulative_report) + return function(driver, device, ib, response) + if is_cumulative_report then + cumulative_energy_imported_handler(driver, device, ib, response) + elseif device:get_field(fields.CUMULATIVE_REPORTS_NOT_SUPPORTED) then + periodic_energy_imported_handler(driver, device, ib, response) + end + end +end + + +-- [[ WATER HEATER MODE CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.water_heater_supported_modes_handler(driver, device, ib, response) + local supportWaterHeaterModes = {} + local supportWaterHeaterModesWithIdx = {} + for _, mode in ipairs(ib.data.elements) do + if version.api < 13 then + clusters.WaterHeaterMode.types.ModeOptionStruct:augment_type(mode) + end + table.insert(supportWaterHeaterModes, mode.elements.label.value) + table.insert(supportWaterHeaterModesWithIdx, {mode.elements.mode.value, mode.elements.label.value}) + end + device:set_field(fields.SUPPORTED_WATER_HEATER_MODES_WITH_IDX, supportWaterHeaterModesWithIdx, { persist = true }) + local event = capabilities.mode.supportedModes(supportWaterHeaterModes, { visibility = { displayed = false } }) + device:emit_event_for_endpoint(ib.endpoint_id, event) + event = capabilities.mode.supportedArguments(supportWaterHeaterModes, { visibility = { displayed = false } }) + device:emit_event_for_endpoint(ib.endpoint_id, event) +end + +function AttributeHandlers.water_heater_current_mode_handler(driver, device, ib, response) + device.log.info(string.format("water_heater_current_mode_handler mode: %s", ib.data.value)) + local supportWaterHeaterModesWithIdx = device:get_field(fields.SUPPORTED_WATER_HEATER_MODES_WITH_IDX) or {} + local currentMode = ib.data.value + for i, mode in ipairs(supportWaterHeaterModesWithIdx) do + if mode[1] == currentMode then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.mode.mode(mode[2])) + break + end + end +end + + +-- [[ RELATIVE HUMIDITY MEASUREMENT CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.relative_humidity_measured_value_handler(driver, device, ib, response) + local humidity = math.floor(ib.data.value / 100.0) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.relativeHumidityMeasurement.humidity(humidity)) +end + + +-- [[ ON OFF CLUSTER ATTRIBUTES ]] -- + +function AttributeHandlers.on_off_handler(driver, device, ib, response) + if ib.data.value then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.on()) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.off()) + end +end + +return AttributeHandlers diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/capability_handlers.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/capability_handlers.lua new file mode 100644 index 0000000000..4a32b7a700 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/capability_handlers.lua @@ -0,0 +1,256 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local log = require "log" +local version = require "version" +local st_utils = require "st.utils" +local clusters = require "st.matter.clusters" +local capabilities = require "st.capabilities" +local fields = require "thermostat_utils.fields" +local thermostat_utils = require "thermostat_utils.utils" + +if version.api < 10 then + clusters.HepaFilterMonitoring = require "embedded_clusters.HepaFilterMonitoring" + clusters.ActivatedCarbonFilterMonitoring = require "embedded_clusters.ActivatedCarbonFilterMonitoring" +end + +if version.api < 13 then + clusters.WaterHeaterMode = require "embedded_clusters.WaterHeaterMode" +end + +local CapabilityHandlers = {} + + +-- [[ FAN SPEED PERCENT CAPABILITY HANDLERS ]] -- + +function CapabilityHandlers.handle_fan_speed_set_percent(driver, device, cmd) + local speed = math.floor(cmd.args.percent) + device:send(clusters.FanControl.attributes.PercentSetting:write(device, thermostat_utils.component_to_endpoint(device, cmd.component, clusters.FanControl.ID), speed)) +end + + +-- [[ WIND MODE CAPABILITY HANDLERS ]] -- + +function CapabilityHandlers.handle_set_wind_mode(driver, device, cmd) + local wind_mode = 0 + if cmd.args.windMode == capabilities.windMode.windMode.sleepWind.NAME then + wind_mode = clusters.FanControl.types.WindSupportMask.SLEEP_WIND + elseif cmd.args.windMode == capabilities.windMode.windMode.naturalWind.NAME then + wind_mode = clusters.FanControl.types.WindSupportMask.NATURAL_WIND + end + device:send(clusters.FanControl.attributes.WindSetting:write(device, thermostat_utils.component_to_endpoint(device, cmd.component, clusters.FanControl.ID), wind_mode)) +end + + +-- [[ FAN OSCILLATION MODE HANDLERS ]] -- + +function CapabilityHandlers.handle_set_fan_oscillation_mode(driver, device, cmd) + local rock_mode = 0 + if cmd.args.fanOscillationMode == capabilities.fanOscillationMode.fanOscillationMode.horizontal.NAME then + rock_mode = clusters.FanControl.types.RockSupportMask.ROCK_LEFT_RIGHT + elseif cmd.args.fanOscillationMode == capabilities.fanOscillationMode.fanOscillationMode.vertical.NAME then + rock_mode = clusters.FanControl.types.RockSupportMask.ROCK_UP_DOWN + elseif cmd.args.fanOscillationMode == capabilities.fanOscillationMode.fanOscillationMode.swing.NAME then + rock_mode = clusters.FanControl.types.RockSupportMask.ROCK_ROUND + end + device:send(clusters.FanControl.attributes.RockSetting:write(device, thermostat_utils.component_to_endpoint(device, cmd.component, clusters.FanControl.ID), rock_mode)) +end + + +-- [[ MODE CAPABILITY HANDLERS ]] -- + +function CapabilityHandlers.handle_set_mode(driver, device, cmd) + device.log.info(string.format("set_water_heater_mode mode: %s", cmd.args.mode)) + local endpoint_id = thermostat_utils.component_to_endpoint(device, cmd.component, clusters.Thermostat.ID) + local supportedWaterHeaterModesWithIdx = device:get_field(fields.SUPPORTED_WATER_HEATER_MODES_WITH_IDX) or {} + for i, mode in ipairs(supportedWaterHeaterModesWithIdx) do + if cmd.args.mode == mode[2] then + device:send(clusters.WaterHeaterMode.commands.ChangeToMode(device, endpoint_id, mode[1])) + return + end + end +end + + +-- [[ FILTER STATE CAPABLITY HANDLERS ]] -- + +function CapabilityHandlers.handle_filter_state_reset_filter(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + if cmd.component == "hepaFilter" then + device:send(clusters.HepaFilterMonitoring.server.commands.ResetCondition(device, endpoint_id)) + else + device:send(clusters.ActivatedCarbonFilterMonitoring.server.commands.ResetCondition(device, endpoint_id)) + end +end + + +-- [[ SWITCH CAPABLITY HANDLERS ]] -- + +function CapabilityHandlers.handle_switch_on(driver, device, cmd) + local endpoint_id = thermostat_utils.component_to_endpoint(device, cmd.component, clusters.OnOff.ID) + local req = clusters.OnOff.server.commands.On(device, endpoint_id) + device:send(req) +end + +function CapabilityHandlers.handle_switch_off(driver, device, cmd) + local endpoint_id = thermostat_utils.component_to_endpoint(device, cmd.component, clusters.OnOff.ID) + local req = clusters.OnOff.server.commands.Off(device, endpoint_id) + device:send(req) +end + + +-- [[ THERMOSTAT MODE CAPABLITY HANDLERS ]] -- + +function CapabilityHandlers.handle_set_thermostat_mode(driver, device, cmd) + local mode_id = nil + for value, mode in pairs(fields.THERMOSTAT_MODE_MAP) do + if mode.NAME == cmd.args.mode then + mode_id = value + break + end + end + if mode_id then + device:send(clusters.Thermostat.attributes.SystemMode:write(device, thermostat_utils.component_to_endpoint(device, cmd.component, clusters.Thermostat.ID), mode_id)) + end +end + +function CapabilityHandlers.thermostat_mode_command_factory(mode_name) + return function(driver, device, cmd) + return CapabilityHandlers.handle_set_thermostat_mode(driver, device, {component = cmd.component, args = {mode = mode_name}}) + end +end + + +-- [[ FAN MODE CAPABILITY HANDLERS ]] -- + +local function set_fan_mode(device, cmd, fan_mode_capability) + local command_argument = cmd.args.fanMode + if fan_mode_capability == capabilities.airPurifierFanMode then + command_argument = cmd.args.airPurifierFanMode + elseif fan_mode_capability == capabilities.thermostatFanMode then + command_argument = cmd.args.mode + end + local fan_mode_id + if command_argument == "off" then + fan_mode_id = clusters.FanControl.attributes.FanMode.OFF + elseif command_argument == "on" then + fan_mode_id = clusters.FanControl.attributes.FanMode.ON + elseif command_argument == "auto" then + fan_mode_id = clusters.FanControl.attributes.FanMode.AUTO + elseif command_argument == "high" then + fan_mode_id = clusters.FanControl.attributes.FanMode.HIGH + elseif command_argument == "medium" then + fan_mode_id = clusters.FanControl.attributes.FanMode.MEDIUM + elseif thermostat_utils.tbl_contains({ "low", "sleep", "quiet", "windFree" }, command_argument) then + fan_mode_id = clusters.FanControl.attributes.FanMode.LOW + else + device.log.warn(string.format("Invalid Fan Mode (%s) received from capability command", command_argument)) + return + end + device:send(clusters.FanControl.attributes.FanMode:write(device, thermostat_utils.component_to_endpoint(device, cmd.component, clusters.FanControl.ID), fan_mode_id)) +end + +function CapabilityHandlers.fan_mode_command_factory(fan_mode_capability) + return function(driver, device, cmd) + set_fan_mode(device, cmd, fan_mode_capability) + end +end + + +-- [[ THERMOSTAT FAN MODE CAPABILITY HANDLERS ]] -- + +function CapabilityHandlers.thermostat_fan_mode_command_factory(mode_name) + return function(driver, device, cmd) + set_fan_mode(device, {component = cmd.component, args = {mode = mode_name}}, capabilities.thermostatFanMode) + end +end + + +-- [[ THERMOSTAT HEATING/COOLING CAPABILITY HANDLERS ]] -- + +function CapabilityHandlers.thermostat_set_setpoint_factory(setpoint) + return function(driver, device, cmd) + local endpoint_id = thermostat_utils.component_to_endpoint(device, cmd.component, clusters.Thermostat.ID) + local MAX_TEMP_IN_C = fields.THERMOSTAT_MAX_TEMP_IN_C + local MIN_TEMP_IN_C = fields.THERMOSTAT_MIN_TEMP_IN_C + local is_water_heater_device = thermostat_utils.get_device_type(device) == fields.WATER_HEATER_DEVICE_TYPE_ID + if is_water_heater_device then + MAX_TEMP_IN_C = fields.WATER_HEATER_MAX_TEMP_IN_C + MIN_TEMP_IN_C = fields.WATER_HEATER_MIN_TEMP_IN_C + end + local value = cmd.args.setpoint + if version.rpc <= 5 and value > MAX_TEMP_IN_C then + value = st_utils.f_to_c(value) + end + + -- Gather cached setpoint values when considering setpoint limits + -- Note: cached values should always exist, but defaults are chosen just in case to prevent + -- nil operation errors, and deadband logic from triggering. + local cached_cooling_val, cooling_setpoint = device:get_latest_state( + cmd.component, capabilities.thermostatCoolingSetpoint.ID, + capabilities.thermostatCoolingSetpoint.coolingSetpoint.NAME, + MAX_TEMP_IN_C, { value = MAX_TEMP_IN_C, unit = "C" } + ) + if cooling_setpoint and cooling_setpoint.unit == "F" then + cached_cooling_val = st_utils.f_to_c(cached_cooling_val) + end + local cached_heating_val, heating_setpoint = device:get_latest_state( + cmd.component, capabilities.thermostatHeatingSetpoint.ID, + capabilities.thermostatHeatingSetpoint.heatingSetpoint.NAME, + MIN_TEMP_IN_C, { value = MIN_TEMP_IN_C, unit = "C" } + ) + if heating_setpoint and heating_setpoint.unit == "F" then + cached_heating_val = st_utils.f_to_c(cached_heating_val) + end + local is_auto_capable = #device:get_endpoints( + clusters.Thermostat.ID, + {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.AUTOMODE} + ) > 0 + + --Check setpoint limits for the device + local setpoint_type = string.match(setpoint.NAME, "Heat") or "Cool" + local deadband = device:get_field(fields.setpoint_limit_device_field.MIN_DEADBAND) or 2.5 --spec default + if setpoint_type == "Heat" then + local min = device:get_field(fields.setpoint_limit_device_field.MIN_HEAT) or MIN_TEMP_IN_C + local max = device:get_field(fields.setpoint_limit_device_field.MAX_HEAT) or MAX_TEMP_IN_C + if value < min or value > max then + log.warn(string.format( + "Invalid setpoint (%s) outside the min (%s) and the max (%s)", + value, min, max + )) + device:emit_event_for_endpoint(endpoint_id, capabilities.thermostatHeatingSetpoint.heatingSetpoint(heating_setpoint, {state_change = true})) + return + end + if is_auto_capable and value > (cached_cooling_val - deadband) then + log.warn(string.format( + "Invalid setpoint (%s) is greater than the cooling setpoint (%s) with the deadband (%s)", + value, cooling_setpoint, deadband + )) + device:emit_event_for_endpoint(endpoint_id, capabilities.thermostatHeatingSetpoint.heatingSetpoint(heating_setpoint, {state_change = true})) + return + end + else + local min = device:get_field(fields.setpoint_limit_device_field.MIN_COOL) or MIN_TEMP_IN_C + local max = device:get_field(fields.setpoint_limit_device_field.MAX_COOL) or MAX_TEMP_IN_C + if value < min or value > max then + log.warn(string.format( + "Invalid setpoint (%s) outside the min (%s) and the max (%s)", + value, min, max + )) + device:emit_event_for_endpoint(endpoint_id, capabilities.thermostatCoolingSetpoint.coolingSetpoint(cooling_setpoint, {state_change = true})) + return + end + if is_auto_capable and value < (cached_heating_val + deadband) then + log.warn(string.format( + "Invalid setpoint (%s) is less than the heating setpoint (%s) with the deadband (%s)", + value, heating_setpoint, deadband + )) + device:emit_event_for_endpoint(endpoint_id, capabilities.thermostatCoolingSetpoint.coolingSetpoint(cooling_setpoint, {state_change = true})) + return + end + end + device:send(setpoint:write(device, thermostat_utils.component_to_endpoint(device, cmd.component, clusters.Thermostat.ID), st_utils.round(value * 100.0))) + end +end + +return CapabilityHandlers diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/device_configuration.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/device_configuration.lua new file mode 100644 index 0000000000..3ae5c76c7e --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/device_configuration.lua @@ -0,0 +1,331 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local embedded_cluster_utils = require "thermostat_utils.embedded_cluster_utils" +local version = require "version" +local fields = require "thermostat_utils.fields" +local thermostat_utils = require "thermostat_utils.utils" + +if version.api < 10 then + clusters.HepaFilterMonitoring = require "embedded_clusters.HepaFilterMonitoring" + clusters.ActivatedCarbonFilterMonitoring = require "embedded_clusters.ActivatedCarbonFilterMonitoring" +end + +if version.api < 11 then + clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement" +end + +if version.api < 13 then + clusters.WaterHeaterMode = require "embedded_clusters.WaterHeaterMode" +end + +local DeviceConfigurationHelpers = {} + +function DeviceConfigurationHelpers.supported_level_measurements(device) + local measurement_caps, level_caps = {}, {} + for _, details in ipairs(fields.AIR_QUALITY_MAP) do + local cap_id = details[1] + local cluster = details[3] + -- capability describes either a HealthConcern or Measurement/Sensor + if (cap_id:match("HealthConcern$")) then + local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) + if #attr_eps > 0 then + table.insert(level_caps, cap_id) + end + elseif (cap_id:match("Measurement$") or cap_id:match("Sensor$")) then + local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.NUMERIC_MEASUREMENT }) + if #attr_eps > 0 then + table.insert(measurement_caps, cap_id) + end + end + end + return measurement_caps, level_caps +end + +function DeviceConfigurationHelpers.get_thermostat_optional_capabilities(device) + local heat_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.HEATING}) + local cool_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.COOLING}) + local running_state_supported = device:get_field(fields.profiling_data.THERMOSTAT_RUNNING_STATE_SUPPORT) + + local supported_thermostat_capabilities = {} + + if #heat_eps > 0 then + table.insert(supported_thermostat_capabilities, capabilities.thermostatHeatingSetpoint.ID) + end + if #cool_eps > 0 then + table.insert(supported_thermostat_capabilities, capabilities.thermostatCoolingSetpoint.ID) + end + + if running_state_supported then + table.insert(supported_thermostat_capabilities, capabilities.thermostatOperatingState.ID) + end + + return supported_thermostat_capabilities +end + +function DeviceConfigurationHelpers.get_air_quality_optional_capabilities(device) + local supported_air_quality_capabilities = {} + + local measurement_caps, level_caps = DeviceConfigurationHelpers.supported_level_measurements(device) + + for _, cap_id in ipairs(measurement_caps) do + table.insert(supported_air_quality_capabilities, cap_id) + end + + for _, cap_id in ipairs(level_caps) do + table.insert(supported_air_quality_capabilities, cap_id) + end + + return supported_air_quality_capabilities +end + + +local DeviceConfiguration = {} + +function DeviceConfiguration.match_modular_profile_air_purifer(device) + local optional_supported_component_capabilities = {} + local main_component_capabilities = {} + local hepa_filter_component_capabilities = {} + local ac_filter_component_capabilties = {} + local profile_name = "air-purifier-modular" + + local MAIN_COMPONENT_IDX = 1 + local CAPABILITIES_LIST_IDX = 2 + + local humidity_eps = device:get_endpoints(clusters.RelativeHumidityMeasurement.ID) + local temp_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureMeasurement.ID) + if #humidity_eps > 0 then + table.insert(main_component_capabilities, capabilities.relativeHumidityMeasurement.ID) + end + if #temp_eps > 0 then + table.insert(main_component_capabilities, capabilities.temperatureMeasurement.ID) + end + + local hepa_filter_eps = embedded_cluster_utils.get_endpoints(device, clusters.HepaFilterMonitoring.ID) + local ac_filter_eps = embedded_cluster_utils.get_endpoints(device, clusters.ActivatedCarbonFilterMonitoring.ID) + + if #hepa_filter_eps > 0 then + local filter_state_eps = embedded_cluster_utils.get_endpoints(device, clusters.HepaFilterMonitoring.ID, {feature_bitmap = clusters.HepaFilterMonitoring.types.Feature.CONDITION}) + if #filter_state_eps > 0 then + table.insert(hepa_filter_component_capabilities, capabilities.filterState.ID) + end + + table.insert(hepa_filter_component_capabilities, capabilities.filterStatus.ID) + end + if #ac_filter_eps > 0 then + local filter_state_eps = embedded_cluster_utils.get_endpoints(device, clusters.ActivatedCarbonFilterMonitoring.ID, {feature_bitmap = clusters.ActivatedCarbonFilterMonitoring.types.Feature.CONDITION}) + if #filter_state_eps > 0 then + table.insert(ac_filter_component_capabilties, capabilities.filterState.ID) + end + + table.insert(ac_filter_component_capabilties, capabilities.filterStatus.ID) + end + + -- determine fan capabilities, note that airPurifierFanMode is already mandatory + local rock_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.Feature.ROCKING}) + local wind_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.FanControlFeature.WIND}) + + if #rock_eps > 0 then + table.insert(main_component_capabilities, capabilities.fanOscillationMode.ID) + end + if #wind_eps > 0 then + table.insert(main_component_capabilities, capabilities.windMode.ID) + end + + local thermostat_eps = device:get_endpoints(clusters.Thermostat.ID) + + if #thermostat_eps > 0 then + -- thermostatMode and temperatureMeasurement should be expected if thermostat is present + table.insert(main_component_capabilities, capabilities.thermostatMode.ID) + + -- only add temperatureMeasurement if it is not already added via TemperatureMeasurement cluster support + if #temp_eps == 0 then + table.insert(main_component_capabilities, capabilities.temperatureMeasurement.ID) + end + local thermostat_capabilities = DeviceConfigurationHelpers.get_thermostat_optional_capabilities(device) + for _, capability_id in pairs(thermostat_capabilities) do + table.insert(main_component_capabilities, capability_id) + end + end + + local aqs_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) + if #aqs_eps > 0 then + table.insert(main_component_capabilities, capabilities.airQualityHealthConcern.ID) + end + + local supported_air_quality_capabilities = DeviceConfigurationHelpers.get_air_quality_optional_capabilities(device) + for _, capability_id in pairs(supported_air_quality_capabilities) do + table.insert(main_component_capabilities, capability_id) + end + + table.insert(optional_supported_component_capabilities, {"main", main_component_capabilities}) + if #ac_filter_component_capabilties > 0 then + table.insert(optional_supported_component_capabilities, {"activatedCarbonFilter", ac_filter_component_capabilties}) + end + if #hepa_filter_component_capabilities > 0 then + table.insert(optional_supported_component_capabilities, {"hepaFilter", hepa_filter_component_capabilities}) + end + + device:try_update_metadata({profile = profile_name, optional_component_capabilities = optional_supported_component_capabilities}) + + -- earlier modular profile gating (min api v14, rpc 8) ensures we are running >= 0.57 FW. + -- This gating specifies a workaround required only for 0.57 FW, which is not needed for 0.58 and higher. + if version.api < 15 or version.rpc < 9 then + -- add mandatory capabilities for subscription + local total_supported_capabilities = optional_supported_component_capabilities + table.insert(total_supported_capabilities[MAIN_COMPONENT_IDX][CAPABILITIES_LIST_IDX], capabilities.airPurifierFanMode.ID) + table.insert(total_supported_capabilities[MAIN_COMPONENT_IDX][CAPABILITIES_LIST_IDX], capabilities.fanSpeedPercent.ID) + table.insert(total_supported_capabilities[MAIN_COMPONENT_IDX][CAPABILITIES_LIST_IDX], capabilities.refresh.ID) + table.insert(total_supported_capabilities[MAIN_COMPONENT_IDX][CAPABILITIES_LIST_IDX], capabilities.firmwareUpdate.ID) + + device:set_field(fields.SUPPORTED_COMPONENT_CAPABILITIES, total_supported_capabilities, { persist = true }) + end +end + +function DeviceConfiguration.match_modular_profile_thermostat(device) + local optional_supported_component_capabilities = {} + local main_component_capabilities = {} + local profile_name = "thermostat-modular" + + local humidity_eps = device:get_endpoints(clusters.RelativeHumidityMeasurement.ID) + if #humidity_eps > 0 then + table.insert(main_component_capabilities, capabilities.relativeHumidityMeasurement.ID) + end + + -- determine fan capabilities + local fan_eps = device:get_endpoints(clusters.FanControl.ID) + local rock_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.Feature.ROCKING}) + local wind_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.FanControlFeature.WIND}) + + if #fan_eps > 0 then + if #device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.Feature.MULTI_SPEED}) > 0 then + table.insert(main_component_capabilities, capabilities.fanSpeedPercent.ID) + if #device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.Feature.AUTO}) > 0 then + table.insert(main_component_capabilities, capabilities.fanMode.ID) + end + else + table.insert(main_component_capabilities, capabilities.fanMode.ID) + end + end + if #rock_eps > 0 then + table.insert(main_component_capabilities, capabilities.fanOscillationMode.ID) + end + if #wind_eps > 0 then + table.insert(main_component_capabilities, capabilities.windMode.ID) + end + + local thermostat_capabilities = DeviceConfigurationHelpers.get_thermostat_optional_capabilities(device) + for _, capability_id in pairs(thermostat_capabilities) do + table.insert(main_component_capabilities, capability_id) + end + + local battery_supported = device:get_field(fields.profiling_data.BATTERY_SUPPORT) + if battery_supported == fields.battery_support.BATTERY_LEVEL then + table.insert(main_component_capabilities, capabilities.batteryLevel.ID) + elseif battery_supported == fields.battery_support.BATTERY_PERCENTAGE then + table.insert(main_component_capabilities, capabilities.battery.ID) + end + + table.insert(optional_supported_component_capabilities, {"main", main_component_capabilities}) + device:try_update_metadata({profile = profile_name, optional_component_capabilities = optional_supported_component_capabilities}) + + -- earlier modular profile gating (min api v14, rpc 8) ensures we are running >= 0.57 FW. + -- This gating specifies a workaround required only for 0.57 FW, which is not needed for 0.58 and higher. + if version.api < 15 or version.rpc < 9 then + -- add mandatory capabilities for subscription + local total_supported_capabilities = optional_supported_component_capabilities + table.insert(main_component_capabilities, capabilities.thermostatMode.ID) + table.insert(main_component_capabilities, capabilities.temperatureMeasurement.ID) + table.insert(main_component_capabilities, capabilities.refresh.ID) + table.insert(main_component_capabilities, capabilities.firmwareUpdate.ID) + + device:set_field(fields.SUPPORTED_COMPONENT_CAPABILITIES, total_supported_capabilities, { persist = true }) + end +end + +function DeviceConfiguration.match_modular_profile_room_ac(device) + local running_state_supported = device:get_field(fields.profiling_data.THERMOSTAT_RUNNING_STATE_SUPPORT) + local humidity_eps = device:get_endpoints(clusters.RelativeHumidityMeasurement.ID) + local optional_supported_component_capabilities = {} + local main_component_capabilities = {} + local profile_name = "room-air-conditioner-modular" + + if #humidity_eps > 0 then + table.insert(main_component_capabilities, capabilities.relativeHumidityMeasurement.ID) + end + + -- determine fan capabilities + local fan_eps = device:get_endpoints(clusters.FanControl.ID) + local wind_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.FanControlFeature.WIND}) + -- Note: Room AC does not support the rocking feature of FanControl. + + if #fan_eps > 0 then + table.insert(main_component_capabilities, capabilities.airConditionerFanMode.ID) + table.insert(main_component_capabilities, capabilities.fanSpeedPercent.ID) + end + if #wind_eps > 0 then + table.insert(main_component_capabilities, capabilities.windMode.ID) + end + + local heat_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.HEATING}) + local cool_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.COOLING}) + + if #heat_eps > 0 then + table.insert(main_component_capabilities, capabilities.thermostatHeatingSetpoint.ID) + end + if #cool_eps > 0 then + table.insert(main_component_capabilities, capabilities.thermostatCoolingSetpoint.ID) + end + + if running_state_supported then + table.insert(main_component_capabilities, capabilities.thermostatOperatingState.ID) + end + + table.insert(optional_supported_component_capabilities, {"main", main_component_capabilities}) + device:try_update_metadata({profile = profile_name, optional_component_capabilities = optional_supported_component_capabilities}) + + -- earlier modular profile gating (min api v14, rpc 8) ensures we are running >= 0.57 FW. + -- This gating specifies a workaround required only for 0.57 FW, which is not needed for 0.58 and higher. + if version.api < 15 or version.rpc < 9 then + -- add mandatory capabilities for subscription + local total_supported_capabilities = optional_supported_component_capabilities + table.insert(main_component_capabilities, capabilities.switch.ID) + table.insert(main_component_capabilities, capabilities.temperatureMeasurement.ID) + table.insert(main_component_capabilities, capabilities.thermostatMode.ID) + table.insert(main_component_capabilities, capabilities.refresh.ID) + table.insert(main_component_capabilities, capabilities.firmwareUpdate.ID) + + device:set_field(fields.SUPPORTED_COMPONENT_CAPABILITIES, total_supported_capabilities, { persist = true }) + end +end + +local match_modular_device_type = { + [fields.AP_DEVICE_TYPE_ID] = DeviceConfiguration.match_modular_profile_air_purifer, + [fields.RAC_DEVICE_TYPE_ID] = DeviceConfiguration.match_modular_profile_room_ac, + [fields.THERMOSTAT_DEVICE_TYPE_ID] = DeviceConfiguration.match_modular_profile_thermostat, +} + +local function profiling_data_still_required(device) + for _, field in pairs(fields.profiling_data) do + if device:get_field(field) == nil then + return true -- data still required if a field is nil + end + end + return false +end + +function DeviceConfiguration.match_profile(device) + if profiling_data_still_required(device) then return end + local primary_device_type = thermostat_utils.get_device_type(device) + if version.api >= 14 and version.rpc >= 8 and match_modular_device_type[primary_device_type] then + match_modular_device_type[primary_device_type](device) + return + else + local legacy_device_cfg = require "thermostat_utils.legacy_device_configuration" + legacy_device_cfg.match_profile(device) + end +end + +return DeviceConfiguration diff --git a/drivers/SmartThings/matter-thermostat/src/embedded-cluster-utils.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/embedded_cluster_utils.lua similarity index 67% rename from drivers/SmartThings/matter-thermostat/src/embedded-cluster-utils.lua rename to drivers/SmartThings/matter-thermostat/src/thermostat_utils/embedded_cluster_utils.lua index 3c38ef55d0..3368fe2fa4 100644 --- a/drivers/SmartThings/matter-thermostat/src/embedded-cluster-utils.lua +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/embedded_cluster_utils.lua @@ -1,31 +1,32 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +local version = require "version" local clusters = require "st.matter.clusters" local utils = require "st.utils" --- Include driver-side definitions when lua libs api version is < 10 -local version = require "version" if version.api < 10 then - clusters.HepaFilterMonitoring = require "HepaFilterMonitoring" - clusters.ActivatedCarbonFilterMonitoring = require "ActivatedCarbonFilterMonitoring" - clusters.AirQuality = require "AirQuality" - clusters.CarbonMonoxideConcentrationMeasurement = require "CarbonMonoxideConcentrationMeasurement" - clusters.CarbonDioxideConcentrationMeasurement = require "CarbonDioxideConcentrationMeasurement" - clusters.FormaldehydeConcentrationMeasurement = require "FormaldehydeConcentrationMeasurement" - clusters.NitrogenDioxideConcentrationMeasurement = require "NitrogenDioxideConcentrationMeasurement" - clusters.OzoneConcentrationMeasurement = require "OzoneConcentrationMeasurement" - clusters.Pm1ConcentrationMeasurement = require "Pm1ConcentrationMeasurement" - clusters.Pm10ConcentrationMeasurement = require "Pm10ConcentrationMeasurement" - clusters.Pm25ConcentrationMeasurement = require "Pm25ConcentrationMeasurement" - clusters.RadonConcentrationMeasurement = require "RadonConcentrationMeasurement" - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "TotalVolatileOrganicCompoundsConcentrationMeasurement" + clusters.HepaFilterMonitoring = require "embedded_clusters.HepaFilterMonitoring" + clusters.ActivatedCarbonFilterMonitoring = require "embedded_clusters.ActivatedCarbonFilterMonitoring" + clusters.AirQuality = require "embedded_clusters.AirQuality" + clusters.CarbonMonoxideConcentrationMeasurement = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement" + clusters.CarbonDioxideConcentrationMeasurement = require "embedded_clusters.CarbonDioxideConcentrationMeasurement" + clusters.FormaldehydeConcentrationMeasurement = require "embedded_clusters.FormaldehydeConcentrationMeasurement" + clusters.NitrogenDioxideConcentrationMeasurement = require "embedded_clusters.NitrogenDioxideConcentrationMeasurement" + clusters.OzoneConcentrationMeasurement = require "embedded_clusters.OzoneConcentrationMeasurement" + clusters.Pm1ConcentrationMeasurement = require "embedded_clusters.Pm1ConcentrationMeasurement" + clusters.Pm10ConcentrationMeasurement = require "embedded_clusters.Pm10ConcentrationMeasurement" + clusters.Pm25ConcentrationMeasurement = require "embedded_clusters.Pm25ConcentrationMeasurement" + clusters.RadonConcentrationMeasurement = require "embedded_clusters.RadonConcentrationMeasurement" + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement" end if version.api < 11 then - clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" - clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" + clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement" + clusters.ElectricalPowerMeasurement = require "embedded_clusters.ElectricalPowerMeasurement" end if version.api < 13 then - clusters.WaterHeaterMode = require "WaterHeaterMode" + clusters.WaterHeaterMode = require "embedded_clusters.WaterHeaterMode" end local embedded_cluster_utils = {} diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/fields.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/fields.lua new file mode 100644 index 0000000000..770fd7d65e --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/fields.lua @@ -0,0 +1,238 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local version = require "version" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local st_utils = require "st.utils" + +if version.api < 10 then + clusters.CarbonDioxideConcentrationMeasurement = require "embedded_clusters.CarbonDioxideConcentrationMeasurement" + clusters.CarbonMonoxideConcentrationMeasurement = require "embedded_clusters.CarbonMonoxideConcentrationMeasurement" + clusters.Pm10ConcentrationMeasurement = require "embedded_clusters.Pm10ConcentrationMeasurement" + clusters.Pm25ConcentrationMeasurement = require "embedded_clusters.Pm25ConcentrationMeasurement" + clusters.FormaldehydeConcentrationMeasurement = require "embedded_clusters.FormaldehydeConcentrationMeasurement" + clusters.NitrogenDioxideConcentrationMeasurement = require "embedded_clusters.NitrogenDioxideConcentrationMeasurement" + clusters.OzoneConcentrationMeasurement = require "embedded_clusters.OzoneConcentrationMeasurement" + clusters.RadonConcentrationMeasurement = require "embedded_clusters.RadonConcentrationMeasurement" + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement" + clusters.Pm1ConcentrationMeasurement = require "embedded_clusters.Pm1ConcentrationMeasurement" + clusters.Thermostat.types.ThermostatSystemMode.DRY = 0x8 -- ThermostatSystemMode added in Matter 1.2 + clusters.Thermostat.types.ThermostatSystemMode.SLEEP = 0x9 -- ThermostatSystemMode added in Matter 1.2 +end + +local ThermostatFields = {} + +ThermostatFields.SUPPORTED_COMPONENT_CAPABILITIES = "__supported_component_capabilities" + +ThermostatFields.SAVED_SYSTEM_MODE_IB = "__saved_system_mode_ib" +ThermostatFields.DISALLOWED_THERMOSTAT_MODES = "__DISALLOWED_CONTROL_OPERATIONS" +ThermostatFields.OPTIONAL_THERMOSTAT_MODES_SEEN = "__OPTIONAL_THERMOSTAT_MODES_SEEN" + +ThermostatFields.RAC_DEVICE_TYPE_ID = 0x0072 +ThermostatFields.AP_DEVICE_TYPE_ID = 0x002D +ThermostatFields.FAN_DEVICE_TYPE_ID = 0x002B +ThermostatFields.WATER_HEATER_DEVICE_TYPE_ID = 0x050F +ThermostatFields.HEAT_PUMP_DEVICE_TYPE_ID = 0x0309 +ThermostatFields.THERMOSTAT_DEVICE_TYPE_ID = 0x0301 +ThermostatFields.ELECTRICAL_SENSOR_DEVICE_TYPE_ID = 0x0510 + +ThermostatFields.MIN_ALLOWED_PERCENT_VALUE = 0 +ThermostatFields.MAX_ALLOWED_PERCENT_VALUE = 100 +ThermostatFields.CUMULATIVE_REPORTS_NOT_SUPPORTED = "__cumulative_reports_not_supported" +ThermostatFields.LAST_IMPORTED_REPORT_TIMESTAMP = "__last_imported_report_timestamp" +ThermostatFields.MINIMUM_ST_ENERGY_REPORT_INTERVAL = (15 * 60) -- 15 minutes, reported in seconds +ThermostatFields.TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP = "__total_cumulative_energy_imported_map" +ThermostatFields.SUPPORTED_WATER_HEATER_MODES_WITH_IDX = "__supported_water_heater_modes_with_idx" +ThermostatFields.COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map" +ThermostatFields.MGM3_PPM_CONVERSION_FACTOR = 24.45 + +-- For RPC version < 6: +-- issue context: driver cannot know a setpoint capability's unit (whether Celsius or Farenheit) +-- when a command is received, as the received arguments do not contain the unit. +-- workaround: map the following temperature ranges to either Celsius or Farenheit: +-- For Thermostats: +-- 1. if the received setpoint command value is in the range 5 ~ 40, it is inferred as *C +-- 2. if the received setpoint command value is in the range 41 ~ 104, it is inferred as *F +-- For Water Heaters: +-- 1. if the received setpoint command value is in the range 30 ~ 80, it is inferred as *C +-- 2. if the received setpoint command value is in the range 86 ~ 176, it is inferred as *F +-- For RPC version >= 6: +-- temperatureSetpoint always reports in Celsius, removing the need for the above workaround. +-- In this case, we use these fields simply to limit the setpoint's range to "reasonable" values on the platform. +ThermostatFields.THERMOSTAT_MAX_TEMP_IN_C = version.rpc >= 6 and 100.0 or 40.0 +ThermostatFields.THERMOSTAT_MIN_TEMP_IN_C = version.rpc >= 6 and 0.0 or 5.0 +ThermostatFields.WATER_HEATER_MAX_TEMP_IN_C = version.rpc >= 6 and 100.0 or 80.0 +ThermostatFields.WATER_HEATER_MIN_TEMP_IN_C = version.rpc >= 6 and 0.0 or 30.0 + +ThermostatFields.setpoint_limit_device_field = { + MIN_SETPOINT_DEADBAND_CHECKED = "MIN_SETPOINT_DEADBAND_CHECKED", + MIN_HEAT = "MIN_HEAT", + MAX_HEAT = "MAX_HEAT", + MIN_COOL = "MIN_COOL", + MAX_COOL = "MAX_COOL", + MIN_DEADBAND = "MIN_DEADBAND", + MIN_TEMP = "MIN_TEMP", + MAX_TEMP = "MAX_TEMP" +} + +ThermostatFields.battery_support = { + NO_BATTERY = "NO_BATTERY", + BATTERY_LEVEL = "BATTERY_LEVEL", + BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE" +} + +ThermostatFields.profiling_data = { + BATTERY_SUPPORT = "__BATTERY_SUPPORT", + THERMOSTAT_RUNNING_STATE_SUPPORT = "__THERMOSTAT_RUNNING_STATE_SUPPORT" +} + +ThermostatFields.THERMOSTAT_MODE_MAP = { + [clusters.Thermostat.types.ThermostatSystemMode.OFF] = capabilities.thermostatMode.thermostatMode.off, + [clusters.Thermostat.types.ThermostatSystemMode.AUTO] = capabilities.thermostatMode.thermostatMode.auto, + [clusters.Thermostat.types.ThermostatSystemMode.COOL] = capabilities.thermostatMode.thermostatMode.cool, + [clusters.Thermostat.types.ThermostatSystemMode.HEAT] = capabilities.thermostatMode.thermostatMode.heat, + [clusters.Thermostat.types.ThermostatSystemMode.EMERGENCY_HEATING] = capabilities.thermostatMode.thermostatMode.emergency_heat, + [clusters.Thermostat.types.ThermostatSystemMode.PRECOOLING] = capabilities.thermostatMode.thermostatMode.precooling, + [clusters.Thermostat.types.ThermostatSystemMode.FAN_ONLY] = capabilities.thermostatMode.thermostatMode.fanonly, + [clusters.Thermostat.types.ThermostatSystemMode.DRY] = capabilities.thermostatMode.thermostatMode.dryair, + [clusters.Thermostat.types.ThermostatSystemMode.SLEEP] = capabilities.thermostatMode.thermostatMode.asleep, +} + +ThermostatFields.THERMOSTAT_OPERATING_MODE_MAP = { + [0] = capabilities.thermostatOperatingState.thermostatOperatingState.heating, + [1] = capabilities.thermostatOperatingState.thermostatOperatingState.cooling, + [2] = capabilities.thermostatOperatingState.thermostatOperatingState.fan_only, + [3] = capabilities.thermostatOperatingState.thermostatOperatingState.heating, + [4] = capabilities.thermostatOperatingState.thermostatOperatingState.cooling, + [5] = capabilities.thermostatOperatingState.thermostatOperatingState.fan_only, + [6] = capabilities.thermostatOperatingState.thermostatOperatingState.fan_only, +} + +ThermostatFields.WIND_MODE_MAP = { + [0] = capabilities.windMode.windMode.sleepWind, + [1] = capabilities.windMode.windMode.naturalWind +} + +ThermostatFields.ROCK_MODE_MAP = { + [0] = capabilities.fanOscillationMode.fanOscillationMode.horizontal, + [1] = capabilities.fanOscillationMode.fanOscillationMode.vertical, + [2] = capabilities.fanOscillationMode.fanOscillationMode.swing +} + +ThermostatFields.AIR_QUALITY_MAP = { + {capabilities.carbonDioxideMeasurement.ID, "-co2", clusters.CarbonDioxideConcentrationMeasurement}, + {capabilities.carbonDioxideHealthConcern.ID, "-co2", clusters.CarbonDioxideConcentrationMeasurement}, + {capabilities.carbonMonoxideMeasurement.ID, "-co", clusters.CarbonMonoxideConcentrationMeasurement}, + {capabilities.carbonMonoxideHealthConcern.ID, "-co", clusters.CarbonMonoxideConcentrationMeasurement}, + {capabilities.dustSensor.ID, "-pm10", clusters.Pm10ConcentrationMeasurement}, + {capabilities.dustHealthConcern.ID, "-pm10", clusters.Pm10ConcentrationMeasurement}, + {capabilities.fineDustSensor.ID, "-pm25", clusters.Pm25ConcentrationMeasurement}, + {capabilities.fineDustHealthConcern.ID, "-pm25", clusters.Pm25ConcentrationMeasurement}, + {capabilities.formaldehydeMeasurement.ID, "-ch2o", clusters.FormaldehydeConcentrationMeasurement}, + {capabilities.formaldehydeHealthConcern.ID, "-ch2o", clusters.FormaldehydeConcentrationMeasurement}, + {capabilities.nitrogenDioxideHealthConcern.ID, "-no2", clusters.NitrogenDioxideConcentrationMeasurement}, + {capabilities.nitrogenDioxideMeasurement.ID, "-no2", clusters.NitrogenDioxideConcentrationMeasurement}, + {capabilities.ozoneHealthConcern.ID, "-ozone", clusters.OzoneConcentrationMeasurement}, + {capabilities.ozoneMeasurement.ID, "-ozone", clusters.OzoneConcentrationMeasurement}, + {capabilities.radonHealthConcern.ID, "-radon", clusters.RadonConcentrationMeasurement}, + {capabilities.radonMeasurement.ID, "-radon", clusters.RadonConcentrationMeasurement}, + {capabilities.tvocHealthConcern.ID, "-tvoc", clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement}, + {capabilities.tvocMeasurement.ID, "-tvoc", clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement}, + {capabilities.veryFineDustHealthConcern.ID, "-pm1", clusters.Pm1ConcentrationMeasurement}, + {capabilities.veryFineDustSensor.ID, "-pm1", clusters.Pm1ConcentrationMeasurement}, +} + +ThermostatFields.units = { + PPM = 0, + PPB = 1, + PPT = 2, + MGM3 = 3, + UGM3 = 4, + NGM3 = 5, + PM3 = 6, + BQM3 = 7, + PCIL = 0xFF -- not in matter spec +} + +local units = ThermostatFields.units -- copy units to avoid references below + +ThermostatFields.unit_strings = { + [units.PPM] = "ppm", + [units.PPB] = "ppb", + [units.PPT] = "ppt", + [units.MGM3] = "mg/m^3", + [units.NGM3] = "ng/m^3", + [units.UGM3] = "μg/m^3", + [units.BQM3] = "Bq/m^3", + [units.PCIL] = "pCi/L" +} + +ThermostatFields.unit_default = { + [capabilities.carbonMonoxideMeasurement.NAME] = units.PPM, + [capabilities.carbonDioxideMeasurement.NAME] = units.PPM, + [capabilities.nitrogenDioxideMeasurement.NAME] = units.PPM, + [capabilities.ozoneMeasurement.NAME] = units.PPM, + [capabilities.formaldehydeMeasurement.NAME] = units.PPM, + [capabilities.veryFineDustSensor.NAME] = units.UGM3, + [capabilities.fineDustSensor.NAME] = units.UGM3, + [capabilities.dustSensor.NAME] = units.UGM3, + [capabilities.radonMeasurement.NAME] = units.BQM3, + [capabilities.tvocMeasurement.NAME] = units.PPB -- TVOC is typically within the range of 0-5500 ppb, with good to moderate values being < 660 ppb +} + +-- All ConcentrationMesurement clusters inherit from the same base cluster definitions, +-- so CarbonMonoxideConcentratinMeasurement is used below but the same enum types exist +-- in all ConcentrationMeasurement clusters +ThermostatFields.level_strings = { + [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.UNKNOWN] = "unknown", + [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.LOW] = "good", + [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.MEDIUM] = "moderate", + [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.HIGH] = "unhealthy", + [clusters.CarbonMonoxideConcentrationMeasurement.types.LevelValueEnum.CRITICAL] = "hazardous", +} + +-- measured in g/mol +ThermostatFields.molecular_weights = { + [capabilities.carbonDioxideMeasurement.NAME] = 44.010, + [capabilities.nitrogenDioxideMeasurement.NAME] = 28.014, + [capabilities.ozoneMeasurement.NAME] = 48.0, + [capabilities.formaldehydeMeasurement.NAME] = 30.031, + [capabilities.veryFineDustSensor.NAME] = "N/A", + [capabilities.fineDustSensor.NAME] = "N/A", + [capabilities.dustSensor.NAME] = "N/A", + [capabilities.radonMeasurement.NAME] = 222.018, + [capabilities.tvocMeasurement.NAME] = "N/A", +} + +ThermostatFields.conversion_tables = { + [units.PPM] = { + [units.PPM] = function(value) return st_utils.round(value) end, + [units.PPB] = function(value) return st_utils.round(value * (10^3)) end, + [units.UGM3] = function(value, molecular_weight) return st_utils.round((value * molecular_weight * 10^3) / ThermostatFields.MGM3_PPM_CONVERSION_FACTOR) end, + [units.MGM3] = function(value, molecular_weight) return st_utils.round((value * molecular_weight) / ThermostatFields.MGM3_PPM_CONVERSION_FACTOR) end, + }, + [units.PPB] = { + [units.PPM] = function(value) return st_utils.round(value/(10^3)) end, + [units.PPB] = function(value) return st_utils.round(value) end, + }, + [units.PPT] = { + [units.PPM] = function(value) return st_utils.round(value/(10^6)) end + }, + [units.MGM3] = { + [units.UGM3] = function(value) return st_utils.round(value * (10^3)) end, + [units.PPM] = function(value, molecular_weight) return st_utils.round((value * ThermostatFields.MGM3_PPM_CONVERSION_FACTOR) / molecular_weight) end, + }, + [units.UGM3] = { + [units.UGM3] = function(value) return st_utils.round(value) end, + [units.PPM] = function(value, molecular_weight) return st_utils.round((value * ThermostatFields.MGM3_PPM_CONVERSION_FACTOR) / (molecular_weight * 10^3)) end, + }, + [units.NGM3] = { + [units.UGM3] = function(value) return st_utils.round(value/(10^3)) end + }, + [units.BQM3] = { + [units.PCIL] = function(value) return st_utils.round(value/37) end + }, +} + +return ThermostatFields \ No newline at end of file diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/legacy_device_configuration.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/legacy_device_configuration.lua new file mode 100644 index 0000000000..6cdda2a53a --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/legacy_device_configuration.lua @@ -0,0 +1,259 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local version = require "version" +local clusters = require "st.matter.clusters" +local embedded_cluster_utils = require "thermostat_utils.embedded_cluster_utils" +local fields = require "thermostat_utils.fields" +local thermostat_utils = require "thermostat_utils.utils" + +if version.api < 10 then + clusters.HepaFilterMonitoring = require "embedded_clusters.HepaFilterMonitoring" + clusters.ActivatedCarbonFilterMonitoring = require "embedded_clusters.ActivatedCarbonFilterMonitoring" + clusters.AirQuality = require "embedded_clusters.AirQuality" +end + +local LegacyConfigurationHelpers = {} + +function LegacyConfigurationHelpers.create_level_measurement_profile(device) + local meas_name, level_name = "", "" + for _, details in ipairs(fields.AIR_QUALITY_MAP) do + local cap_id = details[1] + local cluster = details[3] + -- capability describes either a HealthConcern or Measurement/Sensor + if (cap_id:match("HealthConcern$")) then + local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) + if #attr_eps > 0 then + level_name = level_name .. details[2] + end + elseif (cap_id:match("Measurement$") or cap_id:match("Sensor$")) then + local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.NUMERIC_MEASUREMENT }) + if #attr_eps > 0 then + meas_name = meas_name .. details[2] + end + end + end + return meas_name, level_name +end + +function LegacyConfigurationHelpers.create_air_quality_sensor_profile(device) + local aqs_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) + local profile_name = "" + if #aqs_eps > 0 then + profile_name = profile_name .. "-aqs" + end + local meas_name, level_name = LegacyConfigurationHelpers.create_level_measurement_profile(device) + if meas_name ~= "" then + profile_name = profile_name .. meas_name .. "-meas" + end + if level_name ~= "" then + profile_name = profile_name .. level_name .. "-level" + end + return profile_name +end + +function LegacyConfigurationHelpers.create_fan_profile(device) + local fan_eps = device:get_endpoints(clusters.FanControl.ID) + local wind_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.FanControlFeature.WIND}) + local rock_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.Feature.ROCKING}) + local profile_name = "" + if #fan_eps > 0 then + profile_name = profile_name .. "-fan" + end + if #rock_eps > 0 then + profile_name = profile_name .. "-rock" + end + if #wind_eps > 0 then + profile_name = profile_name .. "-wind" + end + return profile_name +end + +function LegacyConfigurationHelpers.create_air_purifier_profile(device) + local hepa_filter_eps = embedded_cluster_utils.get_endpoints(device, clusters.HepaFilterMonitoring.ID) + local ac_filter_eps = embedded_cluster_utils.get_endpoints(device, clusters.ActivatedCarbonFilterMonitoring.ID) + local fan_eps_seen = false + local profile_name = "air-purifier" + if #hepa_filter_eps > 0 then + profile_name = profile_name .. "-hepa" + end + if #ac_filter_eps > 0 then + profile_name = profile_name .. "-ac" + end + + -- air purifier profiles include -fan later in the name for historical reasons. + -- save this information for use at that point. + local fan_profile = LegacyConfigurationHelpers.create_fan_profile(device) + if fan_profile ~= "" then + fan_eps_seen = true + end + fan_profile = string.gsub(fan_profile, "-fan", "") + profile_name = profile_name .. fan_profile + + return profile_name, fan_eps_seen +end + +function LegacyConfigurationHelpers.create_thermostat_modes_profile(device) + local heat_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.HEATING}) + local cool_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.COOLING}) + + local thermostat_modes = "" + if #heat_eps == 0 and #cool_eps == 0 then + return "No Heating nor Cooling Support" + elseif #heat_eps > 0 and #cool_eps == 0 then + thermostat_modes = thermostat_modes .. "-heating-only" + elseif #cool_eps > 0 and #heat_eps == 0 then + thermostat_modes = thermostat_modes .. "-cooling-only" + end + return thermostat_modes +end + + +local LegacyDeviceConfiguration = {} + +function LegacyDeviceConfiguration.match_profile(device) + local running_state_supported = device:get_field(fields.profiling_data.THERMOSTAT_RUNNING_STATE_SUPPORT) + local battery_supported = device:get_field(fields.profiling_data.BATTERY_SUPPORT) + + local thermostat_eps = device:get_endpoints(clusters.Thermostat.ID) + local humidity_eps = device:get_endpoints(clusters.RelativeHumidityMeasurement.ID) + local device_type = thermostat_utils.get_device_type(device) + local profile_name + if device_type == fields.RAC_DEVICE_TYPE_ID then + profile_name = "room-air-conditioner" + + if #humidity_eps > 0 then + profile_name = profile_name .. "-humidity" + end + + -- Room AC does not support the rocking feature of FanControl. + local fan_name = LegacyConfigurationHelpers.create_fan_profile(device) + fan_name = string.gsub(fan_name, "-rock", "") + profile_name = profile_name .. fan_name + + local thermostat_modes = LegacyConfigurationHelpers.create_thermostat_modes_profile(device) + if thermostat_modes == "" then + profile_name = profile_name .. "-heating-cooling" + else + device.log.warn_with({hub_logs=true}, "Device does not support both heating and cooling. No matching profile") + return + end + + if profile_name == "room-air-conditioner-humidity-fan-wind-heating-cooling" then + profile_name = "room-air-conditioner" + end + + if not running_state_supported and profile_name == "room-air-conditioner-fan-heating-cooling" then + profile_name = profile_name .. "-nostate" + end + + elseif device_type == fields.FAN_DEVICE_TYPE_ID then + profile_name = LegacyConfigurationHelpers.create_fan_profile(device) + -- remove leading "-" + profile_name = string.sub(profile_name, 2) + if profile_name == "fan" then + profile_name = "fan-generic" + end + + elseif device_type == fields.AP_DEVICE_TYPE_ID then + local fan_eps_found + profile_name, fan_eps_found = LegacyConfigurationHelpers.create_air_purifier_profile(device) + if #thermostat_eps > 0 then + profile_name = profile_name .. "-thermostat" + + if #humidity_eps > 0 then + profile_name = profile_name .. "-humidity" + end + + if fan_eps_found then + profile_name = profile_name .. "-fan" + end + + local thermostat_modes = LegacyConfigurationHelpers.create_thermostat_modes_profile(device) + if thermostat_modes ~= "No Heating nor Cooling Support" then + profile_name = profile_name .. thermostat_modes + end + + if not running_state_supported then + profile_name = profile_name .. "-nostate" + end + + if battery_supported == fields.battery_support.BATTERY_LEVEL then + profile_name = profile_name .. "-batteryLevel" + elseif battery_supported == fields.battery_support.NO_BATTERY then + profile_name = profile_name .. "-nobattery" + end + elseif #device:get_endpoints(clusters.TemperatureMeasurement.ID) > 0 then + profile_name = profile_name .. "-temperature" + + if #humidity_eps > 0 then + profile_name = profile_name .. "-humidity" + end + + if fan_eps_found then + profile_name = profile_name .. "-fan" + end + end + profile_name = profile_name .. LegacyConfigurationHelpers.create_air_quality_sensor_profile(device) + elseif device_type == fields.WATER_HEATER_DEVICE_TYPE_ID then + -- If a Water Heater is composed of Electrical Sensor device type, it must support both ElectricalEnergyMeasurement and + -- ElectricalPowerMeasurement clusters. + local electrical_sensor_eps = thermostat_utils.get_endpoints_by_device_type(device, fields.ELECTRICAL_SENSOR_DEVICE_TYPE_ID) or {} + if #electrical_sensor_eps > 0 then + profile_name = "water-heater-power-energy-powerConsumption" + end + elseif device_type == fields.HEAT_PUMP_DEVICE_TYPE_ID then + profile_name = "heat-pump" + local MAX_HEAT_PUMP_THERMOSTAT_COMPONENTS = 2 + for i = 1, math.min(MAX_HEAT_PUMP_THERMOSTAT_COMPONENTS, #thermostat_eps) do + profile_name = profile_name .. "-thermostat" + if thermostat_utils.tbl_contains(humidity_eps, thermostat_eps[i]) then + profile_name = profile_name .. "-humidity" + end + end + elseif #thermostat_eps > 0 then + profile_name = "thermostat" + + if #humidity_eps > 0 then + profile_name = profile_name .. "-humidity" + end + + -- thermostat profiles support neither wind nor rocking FanControl attributes + local fan_name = LegacyConfigurationHelpers.create_fan_profile(device) + if fan_name ~= "" then + profile_name = profile_name .. "-fan" + end + + local thermostat_modes = LegacyConfigurationHelpers.create_thermostat_modes_profile(device) + if thermostat_modes == "No Heating nor Cooling Support" then + device.log.warn_with({hub_logs=true}, "Device does not support either heating or cooling. No matching profile") + return + else + profile_name = profile_name .. thermostat_modes + end + + if not running_state_supported then + profile_name = profile_name .. "-nostate" + end + + if battery_supported == fields.battery_support.BATTERY_LEVEL then + profile_name = profile_name .. "-batteryLevel" + elseif battery_supported == fields.battery_support.NO_BATTERY then + profile_name = profile_name .. "-nobattery" + end + else + device.log.warn_with({hub_logs=true}, "Device type is not supported in thermostat driver") + return + end + + if profile_name then + device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) + device:try_update_metadata({profile = profile_name}) + end + -- clear all profiling data fields after profiling is complete. + for _, field in pairs(fields.profiling_data) do + device:set_field(field, nil) + end +end + +return LegacyDeviceConfiguration diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua new file mode 100644 index 0000000000..7773c9ba02 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_utils/utils.lua @@ -0,0 +1,203 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local log = require "log" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local fields = require "thermostat_utils.fields" +local embedded_cluster_utils = require "thermostat_utils.embedded_cluster_utils" + +local ThermostatUtils = {} + +function ThermostatUtils.tbl_contains(array, value) + if value == nil then return false end + for _, element in pairs(array or {}) do + if element == value then + return true + end + end + return false +end + +function ThermostatUtils.get_field_for_endpoint(device, field, endpoint) + return device:get_field(string.format("%s_%d", field, endpoint)) +end + +function ThermostatUtils.set_field_for_endpoint(device, field, endpoint, value, additional_params) + device:set_field(string.format("%s_%d", field, endpoint), value, additional_params) +end + +function ThermostatUtils.find_default_endpoint(device, cluster) + local res = device.MATTER_DEFAULT_ENDPOINT + local eps = embedded_cluster_utils.get_endpoints(device, cluster) + table.sort(eps) + for _, v in ipairs(eps) do + if v ~= 0 then --0 is the matter RootNode endpoint + return v + end + end + device.log.warn(string.format("Did not find default endpoint, will use endpoint %d instead", device.MATTER_DEFAULT_ENDPOINT)) + return res +end + +function ThermostatUtils.component_to_endpoint(device, component_name, cluster_id) + -- Use the find_default_endpoint function to return the first endpoint that + -- supports a given cluster. + local component_to_endpoint_map = device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) + if component_to_endpoint_map ~= nil and component_to_endpoint_map[component_name] ~= nil then + return component_to_endpoint_map[component_name] + end + if not cluster_id then return device.MATTER_DEFAULT_ENDPOINT end + return ThermostatUtils.find_default_endpoint(device, cluster_id) +end + +function ThermostatUtils.endpoint_to_component(device, endpoint_id) + local component_to_endpoint_map = device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) + if component_to_endpoint_map ~= nil then + for comp, ep in pairs(component_to_endpoint_map) do + if ep == endpoint_id then + return comp + end + end + end + return "main" +end + +function ThermostatUtils.get_total_cumulative_energy_imported(device) + local total_cumulative_energy_imported = device:get_field(fields.TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP) or {} + local total_energy = 0 + for _, energyWh in pairs(total_cumulative_energy_imported) do + total_energy = total_energy + energyWh + end + return total_energy +end + +function ThermostatUtils.get_endpoints_by_device_type(device, device_type) + local endpoints = {} + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == device_type then + table.insert(endpoints, ep.endpoint_id) + break + end + end + end + table.sort(endpoints) + return endpoints +end + + -- set the supportedThermostatOperatingStates attribute if the thermostatOperatingState capability is supported and it has not been set before +function ThermostatUtils.handle_thermostat_operating_state_info(device) + local thermostat_operating_state_supported = device:supports_capability(capabilities.thermostatOperatingState) + local latest_supported_operating_states = thermostat_operating_state_supported and device:get_latest_state( + "main", capabilities.thermostatOperatingState.ID, capabilities.thermostatOperatingState.supportedThermostatOperatingStates.NAME + ) + if thermostat_operating_state_supported and latest_supported_operating_states == nil then + local supported_operating_modes = { "idle" } + if #device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.HEATING}) > 0 then + table.insert(supported_operating_modes, "heating") + end + if #device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.COOLING}) > 0 then + table.insert(supported_operating_modes, "cooling") + end + local thermostat_ep_id = device:get_endpoints(clusters.Thermostat.ID)[1] + device:emit_event_for_endpoint(thermostat_ep_id, capabilities.thermostatOperatingState.supportedThermostatOperatingStates(supported_operating_modes, {visibility = {displayed = false}})) + end +end + +function ThermostatUtils.get_device_type(device) + -- For cases where a device has multiple device types, this list indicates which + -- device type will be the "main" device type for purposes of selecting a profile + -- with an appropriate category. This is done to promote consistency between + -- devices with similar device type compositions that may report their device types + -- listed in different orders + local device_type_priority = { + [fields.HEAT_PUMP_DEVICE_TYPE_ID] = 1, + [fields.RAC_DEVICE_TYPE_ID] = 2, + [fields.AP_DEVICE_TYPE_ID] = 3, + [fields.THERMOSTAT_DEVICE_TYPE_ID] = 4, + [fields.FAN_DEVICE_TYPE_ID] = 5, + [fields.WATER_HEATER_DEVICE_TYPE_ID] = 6, + } + + local main_device_type = false + + for _, ep in ipairs(device.endpoints) do + if ep.device_types ~= nil then + for _, dt in ipairs(ep.device_types) do + if not device_type_priority[main_device_type] or (device_type_priority[dt.device_type_id] and + device_type_priority[dt.device_type_id] < device_type_priority[main_device_type]) then + main_device_type = dt.device_type_id + end + end + end + end + + return main_device_type +end + +function ThermostatUtils.unit_conversion(value, from_unit, to_unit, capability_name) + local conversion_function = fields.conversion_tables[from_unit] and fields.conversion_tables[from_unit][to_unit] or nil + if not conversion_function then + log.info_with( {hub_logs = true} , string.format("Unsupported unit conversion from %s to %s", fields.unit_strings[from_unit], fields.unit_strings[to_unit])) + return + end + + if not value then + log.info_with( {hub_logs = true} , "unit conversion value is nil") + return + end + + return conversion_function(value, fields.molecular_weights[capability_name]) +end + +function ThermostatUtils.supports_capability_by_id_modular(device, capability, component) + if not device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES) then + device.log.warn_with({hub_logs = true}, "Device has overriden supports_capability_by_id, but does not have supported capabilities set.") + return false + end + for _, component_capabilities in ipairs(device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES)) do + local comp_id = component_capabilities[1] + local capability_ids = component_capabilities[2] + if (component == nil) or (component == comp_id) then + for _, cap in ipairs(capability_ids) do + if cap == capability then + return true + end + end + end + end + return false +end + +function ThermostatUtils.report_power_consumption_to_st_energy(device, latest_total_imported_energy_wh) + local current_time = os.time() + local last_time = device:get_field(fields.LAST_IMPORTED_REPORT_TIMESTAMP) or 0 + + -- Ensure that the previous report was sent at least 15 minutes ago + if fields.MINIMUM_ST_ENERGY_REPORT_INTERVAL >= (current_time - last_time) then + return + end + + device:set_field(fields.LAST_IMPORTED_REPORT_TIMESTAMP, current_time, { persist = true }) + + -- Calculate the energy delta between reports + local energy_delta_wh = 0.0 + local previous_imported_report = device:get_latest_state("main", capabilities.powerConsumptionReport.ID, + capabilities.powerConsumptionReport.powerConsumption.NAME) + if previous_imported_report and previous_imported_report.energy then + energy_delta_wh = math.max(latest_total_imported_energy_wh - previous_imported_report.energy, 0.0) + end + + local epoch_to_iso8601 = function(time) return os.date("!%Y-%m-%dT%H:%M:%SZ", time) end + + -- Report the energy consumed during the time interval. The unit of these values should be 'Wh' + device:emit_component_event(device.profile.components["main"], capabilities.powerConsumptionReport.powerConsumption({ + start = epoch_to_iso8601(last_time), + ["end"] = epoch_to_iso8601(current_time - 1), + deltaEnergy = energy_delta_wh, + energy = latest_total_imported_energy_wh + })) +end + +return ThermostatUtils diff --git a/drivers/SmartThings/matter-window-covering/fingerprints.yml b/drivers/SmartThings/matter-window-covering/fingerprints.yml index 63c3aa3254..0de1e91af8 100644 --- a/drivers/SmartThings/matter-window-covering/fingerprints.yml +++ b/drivers/SmartThings/matter-window-covering/fingerprints.yml @@ -36,6 +36,12 @@ matterManufacturer: vendorId: 0x153B productId: 0x3801 deviceProfileName: window-covering-tilt +# HooRii + - id: "4945/61171" + deviceLabel: HooRii Window Covering + vendorId: 0x1351 + productId: 0xEEF3 + deviceProfileName: window-covering-battery # Meross - id: "4933/61453" deviceLabel: Smart Wi-Fi Roller Shutter Timer @@ -162,6 +168,51 @@ matterManufacturer: vendorId: 0x1457 productId: 0x0002 deviceProfileName: window-covering-battery + - id: "5207/22" + deviceLabel: WISTAR WSCMXH Smart Vertical Blind Motor + vendorId: 0x1457 + productId: 0x0016 + deviceProfileName: window-covering-tilt + - id: "5207/23" + deviceLabel: WISTAR WSCMXF Smart Vertical Blind Motor + vendorId: 0x1457 + productId: 0x0017 + deviceProfileName: window-covering-tilt + - id: "5207/24" + deviceLabel: WISTAR WSCMXF-LED Smart Vertical Blind Motor + vendorId: 0x1457 + productId: 0x0018 + deviceProfileName: window-covering-tilt + - id: "5207/20" + deviceLabel: WISTAR WSCMQ Smart Curtain Motor + vendorId: 0x1457 + productId: 0x0014 + deviceProfileName: window-covering + - id: "5207/21" + deviceLabel: WISTAR WSCMXI Smart Curtain Motor + vendorId: 0x1457 + productId: 0x0015 + deviceProfileName: window-covering + - id: "5207/32" + deviceLabel: WISTAR WSCMT Smart Curtain Motor + vendorId: 0x1457 + productId: 0x0020 + deviceProfileName: window-covering + - id: "5207/34" + deviceLabel: WISTAR WSCMXB Smart Curtain Motor + vendorId: 0x1457 + productId: 0x0022 + deviceProfileName: window-covering + - id: "5207/35" + deviceLabel: WISTAR WSCMXC Smart Curtain Motor + vendorId: 0x1457 + productId: 0x0023 + deviceProfileName: window-covering + - id: "5207/38" + deviceLabel: WISTAR WSCMXJ Smart Curtain Motor + vendorId: 0x1457 + productId: 0x0026 + deviceProfileName: window-covering #Yooksmart - id: "5411/1052" deviceLabel: Smart WindowCovering Series diff --git a/drivers/SmartThings/matter-window-covering/src/init.lua b/drivers/SmartThings/matter-window-covering/src/init.lua index 289c164e64..6759560c50 100644 --- a/drivers/SmartThings/matter-window-covering/src/init.lua +++ b/drivers/SmartThings/matter-window-covering/src/init.lua @@ -29,7 +29,7 @@ local battery_support = { } local REVERSE_POLARITY = "__reverse_polarity" local PRESET_LEVEL_KEY = "__preset_level_key" -local PRESET_LEVEL = 50 +local DEFAULT_PRESET_LEVEL = 50 local function find_default_endpoint(device, cluster) local res = device.MATTER_DEFAULT_ENDPOINT @@ -78,7 +78,7 @@ local function device_init(driver, device) device:emit_event(capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed = false}})) local preset_position = device:get_field(PRESET_LEVEL_KEY) or (device.preferences ~= nil and device.preferences.presetPosition) or - PRESET_LEVEL + DEFAULT_PRESET_LEVEL device:emit_event(capabilities.windowShadePreset.position(preset_position, {visibility = {displayed = false}})) device:set_field(PRESET_LEVEL_KEY, preset_position, {persist = true}) end @@ -131,25 +131,14 @@ local function device_removed(driver, device) log.info("device removed") end -- capability handlers local function handle_preset(driver, device, cmd) + local lift_value = device:get_latest_state( + "main", capabilities.windowShadePreset.ID, capabilities.windowShadePreset.position.NAME + ) or DEFAULT_PRESET_LEVEL + local hundredths_lift_percent = (100 - lift_value) * 100 local endpoint_id = device:component_to_endpoint(cmd.component) - local lift_eps = device:get_endpoints(clusters.WindowCovering.ID, {feature_bitmap = clusters.WindowCovering.types.Feature.LIFT}) - local tilt_eps = device:get_endpoints(clusters.WindowCovering.ID, {feature_bitmap = clusters.WindowCovering.types.Feature.TILT}) - if #lift_eps > 0 then - local lift_value = device:get_latest_state( - "main", capabilities.windowShadePreset.ID, capabilities.windowShadePreset.position.NAME - ) or PRESET_LEVEL - local hundredths_lift_percent = (100 - lift_value) * 100 - local req = clusters.WindowCovering.server.commands.GoToLiftPercentage( - device, endpoint_id, hundredths_lift_percent - ) - device:send(req) - end - if #tilt_eps > 0 then - local req = clusters.WindowCovering.server.commands.GoToTiltPercentage( - device, endpoint_id, PRESET_LEVEL * 100 - ) - device:send(req) - end + device:send(clusters.WindowCovering.server.commands.GoToLiftPercentage( + device, endpoint_id, hundredths_lift_percent + )) end local function handle_set_preset(driver, device, cmd) diff --git a/drivers/SmartThings/matter-window-covering/src/test/test_matter_window_covering.lua b/drivers/SmartThings/matter-window-covering/src/test/test_matter_window_covering.lua index b30f2ea4bf..409ebfcb09 100644 --- a/drivers/SmartThings/matter-window-covering/src/test/test_matter_window_covering.lua +++ b/drivers/SmartThings/matter-window-covering/src/test/test_matter_window_covering.lua @@ -789,9 +789,6 @@ test.register_coroutine_test( test.socket.matter:__expect_send( {mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, (100 - PRESET_LEVEL) * 100)} ) - test.socket.matter:__expect_send( - {mock_device.id, WindowCovering.server.commands.GoToTiltPercentage(mock_device, 10, 5000)} - ) end ) diff --git a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/button.lua b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/button.lua index 6c1d41dbb8..d10ad5ee65 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/button.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/button.lua @@ -119,6 +119,25 @@ function ButtonLifecycleHandlers.init(driver, device) if not hue_id_to_device[device_button_resource_id] then hue_id_to_device[device_button_resource_id] = device end + + local maybe_idx_map = device:get_field(Fields.BUTTON_INDEX_MAP) + local svc_rids_for_device = driver.services_for_device_rid[hue_device_id] or {} + + if not svc_rids_for_device[device_button_resource_id] then + svc_rids_for_device[device_button_resource_id] = HueDeviceTypes.BUTTON + end + + for resource_id, _ in pairs(maybe_idx_map or {}) do + if not hue_id_to_device[resource_id] then + hue_id_to_device[resource_id] = device + end + + if not svc_rids_for_device[resource_id] then + svc_rids_for_device[resource_id] = HueDeviceTypes.BUTTON + end + end + driver.services_for_device_rid[hue_device_id] = svc_rids_for_device + local button_info, err button_info = Discovery.device_state_disco_cache[device_button_resource_id] if not button_info then diff --git a/drivers/SmartThings/philips-hue/src/hue/api.lua b/drivers/SmartThings/philips-hue/src/hue/api.lua index dbcc5c4c19..e05fa3d0a0 100644 --- a/drivers/SmartThings/philips-hue/src/hue/api.lua +++ b/drivers/SmartThings/philips-hue/src/hue/api.lua @@ -132,7 +132,7 @@ function PhilipsHueApi.new_bridge_manager(base_url, api_key, socket_builder) true )) local control_tx, control_rx = channel.new() - control_rx:settimeout(30) + control_rx:settimeout(45) local self = setmetatable( { headers = { [APPLICATION_KEY_HEADER] = api_key or "" }, @@ -217,7 +217,7 @@ end ---@return ... local function do_get(instance, path) local reply_tx, reply_rx = channel.new() - reply_rx:settimeout(10) + reply_rx:settimeout(45) local msg = ControlMessageBuilders.Get(path, reply_tx); try_send(instance, msg) local recv, err = reply_rx:receive() @@ -236,7 +236,7 @@ end ---@return ... local function do_put(instance, path, payload) local reply_tx, reply_rx = channel.new() - reply_rx:settimeout(10) + reply_rx:settimeout(45) local msg = ControlMessageBuilders.Put(path, payload, reply_tx); try_send(instance, msg) local recv, err = reply_rx:receive() @@ -255,7 +255,7 @@ end ---@return ... function PhilipsHueApi.get_bridge_info(bridge_ip, socket_builder) local tx, rx = channel.new() - rx:settimeout(10) + rx:settimeout(45) cosock.spawn( function() tx:send(table.pack(process_rest_response(RestClient.one_shot_get("https://" .. bridge_ip .. "/api/config", nil, @@ -278,7 +278,7 @@ end ---@return ... function PhilipsHueApi.request_api_key(bridge_ip, socket_builder) local tx, rx = channel.new() - rx:settimeout(10) + rx:settimeout(45) cosock.spawn( function() local body = json.encode { devicetype = "smartthings_edge_driver#" .. bridge_ip, generateclientkey = true } diff --git a/drivers/SmartThings/philips-hue/src/utils/grouped_utils.lua b/drivers/SmartThings/philips-hue/src/utils/grouped_utils.lua index d3fb4813c9..f169381eed 100644 --- a/drivers/SmartThings/philips-hue/src/utils/grouped_utils.lua +++ b/drivers/SmartThings/philips-hue/src/utils/grouped_utils.lua @@ -287,7 +287,7 @@ function grouped_utils.queue_group_scan(driver, bridge_device) if queue == nil then local tx, rx = cosock.channel.new() -- Set timeout to 30 seconds to allow for other queued scans to come in. - rx:settimeout(30) + rx:settimeout(45) cosock.spawn(function() while true do -- The goal here is to timeout on the receive. If we receive a message then another request diff --git a/drivers/SmartThings/sonos/src/api/event_handlers.lua b/drivers/SmartThings/sonos/src/api/event_handlers.lua index ad9b697740..eeb461f7c9 100644 --- a/drivers/SmartThings/sonos/src/api/event_handlers.lua +++ b/drivers/SmartThings/sonos/src/api/event_handlers.lua @@ -65,7 +65,7 @@ function CapEventHandlers.handle_group_update(device, group_info) end function CapEventHandlers.handle_audio_clip_status(device, clips) - for _, clip in ipairs(clips) do + for _, clip in ipairs(clips or {}) do if clip.status == "ACTIVE" then log.debug(st_utils.stringify_table(clip, "Playing Audio Clip: ", false)) elseif clip.status == "DONE" then diff --git a/drivers/SmartThings/sonos/src/api/sonos_connection.lua b/drivers/SmartThings/sonos/src/api/sonos_connection.lua index 48c8828fc8..471c3a04fb 100644 --- a/drivers/SmartThings/sonos/src/api/sonos_connection.lua +++ b/drivers/SmartThings/sonos/src/api/sonos_connection.lua @@ -55,7 +55,7 @@ local _update_subscriptions_helper = function( command, reply_tx ) - for _, namespace in ipairs(namespaces) do + for _, namespace in ipairs(namespaces or {}) do local wss_msg_header = { namespace = namespace, command = command, @@ -259,7 +259,9 @@ local function _oauth_reconnect_task(sonos_conn) if unauthorized then sonos_conn.driver:alert_unauthorized() - local token, channel_error = token_receive_handle:receive() + local token, channel_error = + (token_receive_handle and token_receive_handle:receive()) or nil, + "no token receive handle" if not token then log.warn(string.format("Error requesting token: %s", channel_error)) local _, get_token_err = sonos_conn.driver:get_oauth_token() @@ -405,7 +407,7 @@ function SonosConnection.new(driver, device) ) return end - local group = household.groups[header.groupId] or { playerIds = {} } + local group = household.groups[header.groupId] or { player_ids = {} } for _, player_id in ipairs(group.player_ids) do local device_for_player = self.driver:device_for_player(header.householdId, player_id) --- we've seen situations where these messages can be processed while a device @@ -429,7 +431,7 @@ function SonosConnection.new(driver, device) ) return end - local group = household.groups[header.groupId] or { playerIds = {} } + local group = household.groups[header.groupId] or { player_ids = {} } for _, player_id in ipairs(group.player_ids) do local device_for_player = self.driver:device_for_player(header.householdId, player_id) --- we've seen situations where these messages can be processed while a device @@ -452,7 +454,7 @@ function SonosConnection.new(driver, device) ) return end - local group = household.groups[header.groupId] or { playerIds = {} } + local group = household.groups[header.groupId] or { player_ids = {} } for _, player_id in ipairs(group.player_ids) do local device_for_player = self.driver:device_for_player(header.householdId, player_id) --- we've seen situations where these messages can be processed while a device @@ -467,9 +469,10 @@ function SonosConnection.new(driver, device) if body.version ~= favorites_version then favorites_version = body.version - local household = self.driver.sonos:get_household(header.householdId) or { groups = {} } + local household = self.driver.sonos:get_household(header.householdId) + or self.driver.sonos.EMPTY_HOUSEHOLD - for group_id, group in pairs(household.groups) do + for group_id, group in pairs(household.groups or {}) do local coordinator_id = self.driver.sonos:get_coordinator_for_group(header.householdId, group_id) local coordinator_player = household.players[coordinator_id] diff --git a/drivers/SmartThings/sonos/src/api/sonos_ssdp_discovery.lua b/drivers/SmartThings/sonos/src/api/sonos_ssdp_discovery.lua index 5baf30efe8..ddc1d0842d 100644 --- a/drivers/SmartThings/sonos/src/api/sonos_ssdp_discovery.lua +++ b/drivers/SmartThings/sonos/src/api/sonos_ssdp_discovery.lua @@ -115,7 +115,7 @@ function SpeakerDiscoveryInfo.new(ssdp_info, discovery_info) ret.rest_path = rest_path end - for k, v in pairs(SpeakerDiscoveryInfo) do + for k, v in pairs(SpeakerDiscoveryInfo or {}) do rawset(ret, k, v) end @@ -227,7 +227,7 @@ local function make_persistent_task_impl( log.warn(string.format("Select error: %s", select_err)) end - for _, receiver in ipairs(recv_ready) do + for _, receiver in ipairs(recv_ready or {}) do if receiver == interval_timer then interval_timer:handled() ssdp_search_handle:multicast_m_search() @@ -331,7 +331,7 @@ function SonosPersistentSsdpTask:get_all_known() -- make a shallow copy of the table so it doesn't get clobbered -- the player info itself is a read-only proxy table as well local known = {} - for id, info in pairs(self.player_info_by_sonos_ids) do + for id, info in pairs(self.player_info_by_sonos_ids or {}) do known[id] = info end return known @@ -454,7 +454,7 @@ function sonos_ssdp.spawn_persistent_ssdp_task() log.debug( st_utils.stringify_table(waiting_handles, "waiting for unique keys and mac addresses", true) ) - for _, reply_tx in ipairs(waiting_handles) do + for _, reply_tx in ipairs(waiting_handles or {}) do reply_tx:send(speaker_info) end diff --git a/drivers/SmartThings/sonos/src/api/sonos_websocket_router.lua b/drivers/SmartThings/sonos/src/api/sonos_websocket_router.lua index 7e15855e89..c19e35274b 100644 --- a/drivers/SmartThings/sonos/src/api/sonos_websocket_router.lua +++ b/drivers/SmartThings/sonos/src/api/sonos_websocket_router.lua @@ -39,7 +39,7 @@ local pending_close = {} cosock.spawn(function() while true do - for _, unique_key in ipairs(pending_close) do -- close any sockets pending close before selecting/receiving on them + for _, unique_key in ipairs(pending_close or {}) do -- close any sockets pending close before selecting/receiving on them local wss = websockets[unique_key] if wss ~= nil then log.trace(string.format("Closing websocket for player %s", unique_key)) @@ -60,7 +60,7 @@ cosock.spawn(function() pending_close = {} local socks = { control_rx } - for _, wss in pairs(websockets) do + for _, wss in pairs(websockets or {}) do table.insert(socks, wss) end local receivers, _, err = socket.select(socks, nil, 10) @@ -71,7 +71,7 @@ cosock.spawn(function() log.error("Error in Websocket Router event loop: " .. err) end else - for _, recv in ipairs(receivers) do + for _, recv in ipairs(receivers or {}) do if recv.link and recv.link.queue and #recv.link.queue == 0 then -- workaround a bug in receiving log.warn("attempting to receive on empty channel") goto continue @@ -93,7 +93,7 @@ cosock.spawn(function() elseif err == "closed" and recv.id then -- closed websocket log.trace(string.format("Websocket %s closed", tostring(recv.id))) local still_open_sockets = {} - for unique_key, wss in pairs(websockets) do + for unique_key, wss in pairs(websockets or {}) do if wss.id ~= recv.id then still_open_sockets[unique_key] = wss end @@ -207,7 +207,7 @@ local function _make_websocket(url_table, api_key) local headers = SonosApi.make_headers(api_key) local config = LustreConfig.default():protocol("v1.api.smartspeaker.audio") - for k, v in pairs(headers) do + for k, v in pairs(headers or {}) do config = config:header(k, v) end @@ -313,7 +313,7 @@ end function SonosWebSocketRouter.cleanup_unused_sockets(driver) log.trace("Begin cleanup of unused websockets") local should_keep = {} - for unique_key, _ in pairs(websockets) do + for unique_key, _ in pairs(websockets or {}) do local household_id, player_id = unique_key:match("(.*)/(.*)") local is_joined = driver.sonos:get_device_id_for_player(household_id, player_id) ~= nil log.debug(string.format("Is Player %s joined? %s", player_id, is_joined)) @@ -322,7 +322,7 @@ function SonosWebSocketRouter.cleanup_unused_sockets(driver) local known_devices = driver:get_devices() - for _, device in ipairs(known_devices) do + for _, device in ipairs(known_devices or {}) do local household_id, coordinator_id = driver.sonos:get_coordinator_for_device(device) local coordinator_unique_key, bad_key_part = utils.sonos_unique_key(household_id, coordinator_id) @@ -340,7 +340,7 @@ function SonosWebSocketRouter.cleanup_unused_sockets(driver) end end - for unique_key, keep in pairs(should_keep) do + for unique_key, keep in pairs(should_keep or {}) do if not keep then SonosWebSocketRouter.close_socket_for_player(unique_key) end diff --git a/drivers/SmartThings/sonos/src/cosock/bus.lua b/drivers/SmartThings/sonos/src/cosock/bus.lua index adb36c4880..f0a248753c 100644 --- a/drivers/SmartThings/sonos/src/cosock/bus.lua +++ b/drivers/SmartThings/sonos/src/cosock/bus.lua @@ -159,7 +159,7 @@ end function __sender_mt:close() self._bus_inner.closed = true local existing_links = self._bus_inner.receiver_links - for _, link in pairs(existing_links) do + for _, link in pairs(existing_links or {}) do if link.waker then link.waker() end @@ -176,7 +176,7 @@ end function __sender_mt:send(msg) if not self._bus_inner.closed then -- wapping in table allows `nil` to be sent as a message - for _, link in pairs(self._bus_inner.receiver_links) do + for _, link in pairs(self._bus_inner.receiver_links or {}) do table.insert(link.queue, { msg = msg }) if link.waker then link.waker() diff --git a/drivers/SmartThings/sonos/src/lifecycle_handlers.lua b/drivers/SmartThings/sonos/src/lifecycle_handlers.lua index 97a79ac209..1055d53020 100644 --- a/drivers/SmartThings/sonos/src/lifecycle_handlers.lua +++ b/drivers/SmartThings/sonos/src/lifecycle_handlers.lua @@ -165,6 +165,7 @@ function SonosDriverLifecycleHandlers.initialize_device(driver, device) { hub_logs = true }, string.format("Driver wasn't able to spin up SSDP task, cannot initialize devices.") ) + cosock.socket.sleep(30) end end end, diff --git a/drivers/SmartThings/sonos/src/sonos_driver.lua b/drivers/SmartThings/sonos/src/sonos_driver.lua index 3d8b22ecf3..56ccb2335f 100644 --- a/drivers/SmartThings/sonos/src/sonos_driver.lua +++ b/drivers/SmartThings/sonos/src/sonos_driver.lua @@ -1,4 +1,6 @@ -local api_version = require "version".api +local version = require "version" +local api_version = version.api +local rpc_version = version.rpc local capabilities = require "st.capabilities" local cosock = require "cosock" local json = require "st.json" @@ -134,7 +136,7 @@ function SonosDriver:oauth_info_event_subscribe() end function SonosDriver:update_after_startup_state_received() - for k, v in pairs(self.hub_augmented_driver_data) do + for k, v in pairs(self.hub_augmented_driver_data or {}) do local decode_success, decoded = pcall(json.decode, v) if decode_success then self:handle_augmented_data_change(k, decoded) @@ -212,7 +214,7 @@ function SonosDriver:handle_startup_state_received() token_refresher.spawn_token_refresher(self) end self.startup_state_received = true - for _, device in pairs(self.devices_waiting_for_startup_state) do + for _, device in pairs(self.devices_waiting_for_startup_state or {}) do SonosDriverLifecycleHandlers.initialize_device(self, device) end self.devices_waiting_for_startup_state = {} @@ -291,7 +293,7 @@ function SonosDriver:check_auth(info_or_device) end local unauthorized = false - for _, api_key in pairs(SonosApi.api_keys) do + for _, api_key in pairs(SonosApi.api_keys or {}) do local headers = SonosApi.make_headers(api_key, maybe_token and maybe_token.accessToken) local response, response_err = SonosApi.RestApi.get_groups_info(rest_url, household_id, headers) @@ -422,13 +424,13 @@ local function make_ssdp_event_handler( local recv_ready, _, select_err = cosock.socket.select(receivers, nil, nil) if recv_ready then - for _, receiver in ipairs(recv_ready) do + for _, receiver in ipairs(recv_ready or {}) do if oauth_token_subscription ~= nil and receiver == oauth_token_subscription then local token_evt, receive_err = oauth_token_subscription:receive() if not token_evt then log.warn(string.format("Error on token event bus receive: %s", receive_err)) else - for _, event in pairs(unauthorized) do + for _, event in pairs(unauthorized or {}) do -- shouldn't need a nil check on the ssdp_task here since this whole function -- won't get called unless the task is successfully spawned. driver.ssdp_task:publish(event) @@ -479,6 +481,17 @@ local function make_ssdp_event_handler( end function SonosDriver:start_ssdp_event_task() + if self.ssdp_task ~= nil then + return + end + cosock.spawn(function () + while self:start_ssdp_event_task_inner() == false do + cosock.socket.sleep(30) + end + end) +end + +function SonosDriver:start_ssdp_event_task_inner() local ssdp_task, err = sonos_ssdp.spawn_persistent_ssdp_task() if err then log.error_with({ hub_logs = true }, string.format("Unable to create SSDP task: %s", err)) @@ -492,7 +505,9 @@ function SonosDriver:start_ssdp_event_task() end self.ssdp_event_thread_handle = cosock.spawn(make_ssdp_event_handler(self, ssdp_task_subscription, oauth_token_subscription)) + return true end + return false end ---@param api_key string @@ -638,6 +653,59 @@ local function do_refresh(driver, device, cmd) sonos_conn:refresh_subscriptions() end +--- In RPC version 100, the events for augmented driver store were changed +--- to no longer match the parsing done in API version 18 and below. +--- +--- API version 19 will handle both before and after RPC 100 changes so this only needs +--- to be applied for RPC version >= 100 and API version <= 18. +if rpc_version >= 100 and api_version <= 18 then + log.info_with({ hub_logs = true }, "Overriding environment info handler for RPC >= 100 and API <= 18") + function SonosDriver:environment_info_handler(channel) + log.info_with({ hub_logs = true }, "Starting environment info handler for RPC >= 100 and API <= 18") + local msg_type, msg_val = channel:receive() + -- This driver only cares about augmentDriverStore messages currently. + -- Previously, this was augmentDatastore msg_type. + if msg_type == "augmentDriverStore" then + if type(msg_val.payload) ~= "table" then + log.warn( + string.format( + "Unexpected augmentDriverStore payload type: %s", + type(msg_val.payload) + ) + ) + return + end + -- The field evt_kind was renamed to kind and is a string of the enum rather than + -- the integer value of the enum. + if msg_val.kind == "Upsert" then + -- This type was changed to table of u8s instead of a string + local data = "" + for _, v in pairs(msg_val.payload.data_value) do + data = data .. string.char(v) + end + self.hub_augmented_driver_data[msg_val.payload.data_key] = data + -- notify with the updated record + if self.notify_augmented_data_changed ~= nil then + self:notify_augmented_data_changed("upsert", msg_val.payload.data_key, data) + end + elseif msg_val.kind == "Delete" then + self.hub_augmented_driver_data[msg_val.payload.data_key] = nil + -- notify with just the key that got deleted + if self.notify_augmented_data_changed ~= nil then + self:notify_augmented_data_changed("delete", msg_val.payload.data_key) + end + else + log.warn( + string.format( + "Unexpected augmentDriverStore kind: %s", + msg_val.kind + ) + ) + end + end + end +end + function SonosDriver.new_driver_template() local oauth_token_bus = cosock.bus() local oauth_info_bus = cosock.bus() @@ -654,6 +722,8 @@ function SonosDriver.new_driver_template() bonded_devices = utils.new_mac_address_keyed_table(), dni_to_device_id = utils.new_mac_address_keyed_table(), lifecycle_handlers = SonosDriverLifecycleHandlers, + -- Only overriden in the case of RPC version 100+ with API version <= 18 + environment_info_handler = SonosDriver.environment_info_handler, capability_handlers = { [capabilities.refresh.ID] = { [capabilities.refresh.commands.refresh.NAME] = do_refresh, @@ -696,7 +766,7 @@ function SonosDriver.new_driver_template() }, } - for k, v in pairs(SonosDriver) do + for k, v in pairs(SonosDriver or {}) do template[k] = v end diff --git a/drivers/SmartThings/sonos/src/sonos_state.lua b/drivers/SmartThings/sonos/src/sonos_state.lua index 4fe27ccdc6..faf466c6a3 100644 --- a/drivers/SmartThings/sonos/src/sonos_state.lua +++ b/drivers/SmartThings/sonos/src/sonos_state.lua @@ -85,6 +85,7 @@ local _STATE = { --- @class SonosState local SonosState = {} SonosState.__index = SonosState +SonosState.EMPTY_HOUSEHOLD = _STATE.households:get_or_init("__EMPTY") ---@param device SonosDevice ---@param info SpeakerDiscoveryInfo @@ -361,10 +362,10 @@ function SonosState:update_household_info(id, groups_event, driver) local groups, players = groups_event.groups, groups_event.players - for _, group in ipairs(groups) do + for _, group in ipairs(groups or {}) do household.groups[group.id] = { id = group.id, coordinator_id = group.coordinatorId, player_ids = group.playerIds } - for _, playerId in ipairs(group.playerIds) do + for _, playerId in ipairs(group.playerIds or {}) do household.player_to_group[playerId] = group.id end end @@ -372,15 +373,15 @@ function SonosState:update_household_info(id, groups_event, driver) -- Iterate through the players and track all the devices associated with them -- for bonded set tracking. local log_devices_error = false - for _, player in ipairs(players) do + for _, player in ipairs(players or {}) do -- Prefer devices because deviceIds is deprecated but all we care about is -- the ID so either way is fine. if type(player.devices) == "table" then - for _, device in ipairs(player.devices) do + for _, device in ipairs(player.devices or {}) do update_device_info(driver, player, household, known_bonded_players, device.id) end elseif type(player.deviceIds) == "table" then - for _, device_id in ipairs(player.deviceIds) do + for _, device_id in ipairs(player.deviceIds or {}) do update_device_info(driver, player, household, known_bonded_players, device_id) end else diff --git a/drivers/SmartThings/sonos/src/ssdp.lua b/drivers/SmartThings/sonos/src/ssdp.lua index 7e3b06a198..3e11b92d04 100644 --- a/drivers/SmartThings/sonos/src/ssdp.lua +++ b/drivers/SmartThings/sonos/src/ssdp.lua @@ -64,7 +64,7 @@ end ---@diagnostic disable-next-line: inject-field function Ssdp.check_headers_contain(headers, keys_to_check) local missing = {} - for _, header_key in ipairs(keys_to_check) do + for _, header_key in ipairs(keys_to_check or {}) do if headers:get_one(header_key) == nil then table.insert(missing, header_key) end @@ -197,7 +197,7 @@ end --- the search end time will be pushed out based on the `mx` parameter, extending the amount of --- time that `receive_m_search_response` will return results with a timeout. function _ssdp_mt:multicast_m_search() - for term, _ in pairs(self.search_terms) do + for term, _ in pairs(self.search_terms or {}) do local multicast_msg = table.concat({ "M-SEARCH * HTTP/1.1", "HOST: 239.255.255.250:1900", @@ -284,7 +284,7 @@ function _ssdp_mt:next_msearch_response() ) end - for _, location in ipairs(location_candidates) do + for _, location in ipairs(location_candidates or {}) do local location_host_match = location:match("http://([^,/]+):[^/]+/.+%.xml") if location_host_match ~= nil then possible_locations[location_host_match] = location diff --git a/drivers/SmartThings/sonos/src/utils.lua b/drivers/SmartThings/sonos/src/utils.lua index 1aa299b629..0b646252a2 100644 --- a/drivers/SmartThings/sonos/src/utils.lua +++ b/drivers/SmartThings/sonos/src/utils.lua @@ -319,7 +319,7 @@ function utils.deep_table_eq(tbl1, tbl2) if tbl1 == tbl2 then return true elseif type(tbl1) == "table" and type(tbl2) == "table" then - for key1, value1 in pairs(tbl1) do + for key1, value1 in pairs(tbl1 or {}) do local value2 = tbl2[key1] if value2 == nil then @@ -337,7 +337,7 @@ function utils.deep_table_eq(tbl1, tbl2) end -- check for missing keys in tbl1 - for key2, _ in pairs(tbl2) do + for key2, _ in pairs(tbl2 or {}) do if tbl1[key2] == nil then return false end diff --git a/drivers/SmartThings/zigbee-bed/src/shus-mattress/init.lua b/drivers/SmartThings/zigbee-bed/src/shus-mattress/init.lua index c11354a8d2..08109f35ce 100755 --- a/drivers/SmartThings/zigbee-bed/src/shus-mattress/init.lua +++ b/drivers/SmartThings/zigbee-bed/src/shus-mattress/init.lua @@ -36,7 +36,7 @@ end local function process_control_attr_factory(cmd) return function(driver, device, value, zb_rx) - device:emit_event(cmd.idle()) + device:emit_event(cmd("idle", { visibility = { displayed = false }})) end end @@ -129,7 +129,7 @@ local function process_capabilities_hardness_factory(cap,attrs,cap_attr) ) --A button that can be triggered continuously local evt_ctrl = cap_attr.soft() - local evt_idle = cap_attr.idle() + local evt_idle = cap_attr("idle", { visibility = { displayed = false }}) if cmd.args[cap] == "hard" then evt_ctrl = cap_attr.hard() end @@ -148,7 +148,7 @@ local function device_init(driver, device) end local function device_added(driver, device) - device:emit_event(custom_capabilities.yoga.supportedYogaState({"stop", "left", "right"})) + device:emit_event(custom_capabilities.yoga.supportedYogaState({"stop", "left", "right"}, { visibility = { displayed = false }})) do_refresh(driver, device) end diff --git a/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua b/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua index 0ba0441719..d3922a3bae 100755 --- a/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua +++ b/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua @@ -59,7 +59,7 @@ test.register_coroutine_test( function() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.yoga.supportedYogaState({"stop", "left", "right"}) )) + custom_capabilities.yoga.supportedYogaState({"stop", "left", "right"}, { visibility = { displayed = false }}) )) local read_0x0006_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0006, MFG_CODE) local read_0x0007_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0007, MFG_CODE) local read_0x0009_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0009, MFG_CODE) @@ -140,7 +140,7 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "Device reported leftback 0 and driver emit custom_capabilities.left_control.leftback.idle()", + "Device reported leftback 0 and driver emit custom_capabilities.left_control.leftback.idle({ visibility = { displayed = false }})", function() local attr_report_data = { { 0x0000, data_types.Uint8.ID, 0 } @@ -150,12 +150,12 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftback.idle())) + custom_capabilities.left_control.leftback.idle({ visibility = { displayed = false }}))) end ) test.register_coroutine_test( - "Device reported leftback 1 and driver emit custom_capabilities.left_control.leftback.idle()", + "Device reported leftback 1 and driver emit custom_capabilities.left_control.leftback.idle({ visibility = { displayed = false }})", function() local attr_report_data = { { 0x0000, data_types.Uint8.ID, 1 } @@ -165,12 +165,12 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftback.idle())) + custom_capabilities.left_control.leftback.idle({ visibility = { displayed = false }}))) end ) test.register_coroutine_test( - "Device reported leftwaist 0 and driver emit custom_capabilities.left_control.leftwaist.idle()", + "Device reported leftwaist 0 and driver emit custom_capabilities.left_control.leftwaist.idle({ visibility = { displayed = false }})", function() local attr_report_data = { { 0x0001, data_types.Uint8.ID, 0 } @@ -180,12 +180,12 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftwaist.idle())) + custom_capabilities.left_control.leftwaist.idle({ visibility = { displayed = false }}))) end ) test.register_coroutine_test( - "Device reported leftwaist 1 and driver emit custom_capabilities.left_control.leftwaist.idle()", + "Device reported leftwaist 1 and driver emit custom_capabilities.left_control.leftwaist.idle({ visibility = { displayed = false }})", function() local attr_report_data = { { 0x0001, data_types.Uint8.ID, 1 } @@ -195,12 +195,12 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftwaist.idle())) + custom_capabilities.left_control.leftwaist.idle({ visibility = { displayed = false }}))) end ) test.register_coroutine_test( - "Device reported lefthip 0 and driver emit custom_capabilities.left_control.lefthip.idle()", + "Device reported lefthip 0 and driver emit custom_capabilities.left_control.lefthip.idle({ visibility = { displayed = false }})", function() local attr_report_data = { { 0x0002, data_types.Uint8.ID, 0 } @@ -210,12 +210,12 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.lefthip.idle())) + custom_capabilities.left_control.lefthip.idle({ visibility = { displayed = false }}))) end ) test.register_coroutine_test( - "Device reported lefthip 1 and driver emit custom_capabilities.left_control.lefthip.idle()", + "Device reported lefthip 1 and driver emit custom_capabilities.left_control.lefthip.idle({ visibility = { displayed = false }})", function() local attr_report_data = { { 0x0002, data_types.Uint8.ID, 1 } @@ -225,12 +225,12 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.lefthip.idle())) + custom_capabilities.left_control.lefthip.idle({ visibility = { displayed = false }}))) end ) test.register_coroutine_test( - "Device reported rightback 0 and driver emit custom_capabilities.right_control.rightback.idle()", + "Device reported rightback 0 and driver emit custom_capabilities.right_control.rightback.idle({ visibility = { displayed = false }})", function() local attr_report_data = { { 0x0003, data_types.Uint8.ID, 0 } @@ -240,12 +240,12 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightback.idle())) + custom_capabilities.right_control.rightback.idle({ visibility = { displayed = false }}))) end ) test.register_coroutine_test( - "Device reported rightback 1 and driver emit custom_capabilities.right_control.rightback.idle()", + "Device reported rightback 1 and driver emit custom_capabilities.right_control.rightback.idle({ visibility = { displayed = false }})", function() local attr_report_data = { { 0x0003, data_types.Uint8.ID, 1 } @@ -255,12 +255,12 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightback.idle())) + custom_capabilities.right_control.rightback.idle({ visibility = { displayed = false }}))) end ) test.register_coroutine_test( - "Device reported rightwaist 0 and driver emit custom_capabilities.right_control.rightwaist.idle()", + "Device reported rightwaist 0 and driver emit custom_capabilities.right_control.rightwaist.idle({ visibility = { displayed = false }})", function() local attr_report_data = { { 0x0004, data_types.Uint8.ID, 0 } @@ -270,12 +270,12 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightwaist.idle())) + custom_capabilities.right_control.rightwaist.idle({ visibility = { displayed = false }}))) end ) test.register_coroutine_test( - "Device reported rightwaist 1 and driver emit custom_capabilities.right_control.rightwaist.idle()", + "Device reported rightwaist 1 and driver emit custom_capabilities.right_control.rightwaist.idle({ visibility = { displayed = false }})", function() local attr_report_data = { { 0x0004, data_types.Uint8.ID, 1 } @@ -285,12 +285,12 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightwaist.idle())) + custom_capabilities.right_control.rightwaist.idle({ visibility = { displayed = false }}))) end ) test.register_coroutine_test( - "Device reported righthip 0 and driver emit custom_capabilities.right_control.righthip.idle()", + "Device reported righthip 0 and driver emit custom_capabilities.right_control.righthip.idle({ visibility = { displayed = false }})", function() local attr_report_data = { { 0x0005, data_types.Uint8.ID, 0 } @@ -300,12 +300,12 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.righthip.idle())) + custom_capabilities.right_control.righthip.idle({ visibility = { displayed = false }}))) end ) test.register_coroutine_test( - "Device reported righthip 1 and driver emit custom_capabilities.right_control.righthip.idle()", + "Device reported righthip 1 and driver emit custom_capabilities.right_control.righthip.idle({ visibility = { displayed = false }})", function() local attr_report_data = { { 0x0005, data_types.Uint8.ID, 1 } @@ -315,7 +315,7 @@ test.register_coroutine_test( zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.righthip.idle())) + custom_capabilities.right_control.righthip.idle({ visibility = { displayed = false }}))) end ) diff --git a/drivers/SmartThings/zigbee-button/fingerprints.yml b/drivers/SmartThings/zigbee-button/fingerprints.yml index 7acc77387f..c70cae5eac 100644 --- a/drivers/SmartThings/zigbee-button/fingerprints.yml +++ b/drivers/SmartThings/zigbee-button/fingerprints.yml @@ -8,12 +8,32 @@ zigbeeManufacturer: deviceLabel: Aqara Wireless Mini Switch T1 manufacturer: LUMI model: lumi.remote.b1acn02 - deviceProfileName: one-button-battery + deviceProfileName: one-button-batteryLevel - id: "LUMI/lumi.remote.acn003" deviceLabel: Aqara Wireless Remote Switch E1 (Single Rocker) manufacturer: LUMI model: lumi.remote.acn003 - deviceProfileName: one-button-battery + deviceProfileName: one-button-batteryLevel + - id: "LUMI/lumi.remote.b186acn03" + deviceLabel: Aqara Wireless Remote Switch T1 (Single Rocker) + manufacturer: LUMI + model: lumi.remote.b186acn03 + deviceProfileName: one-button-batteryLevel + - id: "LUMI/lumi.remote.b286acn03" + deviceLabel: Aqara Wireless Remote Switch T1 (Double Rocker) + manufacturer: LUMI + model: lumi.remote.b286acn03 + deviceProfileName: aqara-double-buttons + - id: "LUMI/lumi.remote.b18ac1" + deviceLabel: Aqara Wireless Remote Switch H1 (Single Rocker) + manufacturer: LUMI + model: lumi.remote.b18ac1 + deviceProfileName: aqara-single-button-mode + - id: "LUMI/lumi.remote.b28ac1" + deviceLabel: Aqara Wireless Remote Switch H1 (Double Rocker) + manufacturer: LUMI + model: lumi.remote.b28ac1 + deviceProfileName: aqara-double-buttons-mode - id: "HEIMAN/SOS-EM" deviceLabel: HEIMAN Button manufacturer: HEIMAN diff --git a/drivers/SmartThings/zigbee-button/profiles/aqara-double-buttons-mode.yml b/drivers/SmartThings/zigbee-button/profiles/aqara-double-buttons-mode.yml new file mode 100644 index 0000000000..f19f19da57 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/profiles/aqara-double-buttons-mode.yml @@ -0,0 +1,35 @@ +name: aqara-double-buttons-mode +components: + - id: main + capabilities: + - id: button + version: 1 + - id: batteryLevel + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController + - id: button1 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: all + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController +preferences: + - preferenceId: stse.allowOperationModeChange + explicit: true diff --git a/drivers/SmartThings/zigbee-button/profiles/aqara-double-buttons.yml b/drivers/SmartThings/zigbee-button/profiles/aqara-double-buttons.yml new file mode 100644 index 0000000000..07a4882783 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/profiles/aqara-double-buttons.yml @@ -0,0 +1,32 @@ +name: aqara-double-buttons +components: + - id: main + capabilities: + - id: button + version: 1 + - id: batteryLevel + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController + - id: button1 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: all + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController diff --git a/drivers/SmartThings/zigbee-button/profiles/aqara-single-button-mode.yml b/drivers/SmartThings/zigbee-button/profiles/aqara-single-button-mode.yml new file mode 100644 index 0000000000..904de72281 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/profiles/aqara-single-button-mode.yml @@ -0,0 +1,17 @@ +name: aqara-single-button-mode +components: + - id: main + capabilities: + - id: button + version: 1 + - id: batteryLevel + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Button +preferences: + - preferenceId: stse.allowOperationModeChange + explicit: true diff --git a/drivers/SmartThings/zigbee-button/profiles/one-button-batteryLevel.yml b/drivers/SmartThings/zigbee-button/profiles/one-button-batteryLevel.yml new file mode 100644 index 0000000000..005e41256f --- /dev/null +++ b/drivers/SmartThings/zigbee-button/profiles/one-button-batteryLevel.yml @@ -0,0 +1,14 @@ +name: one-button-batteryLevel +components: + - id: main + capabilities: + - id: button + version: 1 + - id: batteryLevel + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Button diff --git a/drivers/SmartThings/zigbee-button/src/aqara/init.lua b/drivers/SmartThings/zigbee-button/src/aqara/init.lua index 174d775324..1fb762cbbc 100644 --- a/drivers/SmartThings/zigbee-button/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-button/src/aqara/init.lua @@ -19,84 +19,193 @@ local data_types = require "st.zigbee.data_types" local capabilities = require "st.capabilities" local button_utils = require "button_utils" +local MODE = "devicemode" +local MODE_CHANGE = "stse.allowOperationModeChange" +local SUPPORTED_BUTTON = { { "pushed" }, { "pushed", "held", "double" } } local PowerConfiguration = clusters.PowerConfiguration local PRIVATE_CLUSTER_ID = 0xFCC0 local PRIVATE_ATTRIBUTE_ID_T1 = 0x0009 local PRIVATE_ATTRIBUTE_ID_E1 = 0x0125 +local PRIVATE_ATTRIBUTE_ID_ALIVE = 0x00F7 local MFG_CODE = 0x115F local MULTISTATE_INPUT_CLUSTER_ID = 0x0012 local PRESENT_ATTRIBUTE_ID = 0x0055 -local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.remote.b1acn02" }, - { mfr = "LUMI", model = "lumi.remote.acn003" } +local COMP_LIST = { "button1", "button2", "all" } +local AQARA_REMOTE_SWITCH = { + ["lumi.remote.b1acn02"] = { mfr = "LUMI", btn_cnt = 1, type = "CR2032", quantity = 1 }, -- Aqara Wireless Mini Switch T1 + ["lumi.remote.acn003"] = { mfr = "LUMI", btn_cnt = 1, type = "CR2032", quantity = 1 }, -- Aqara Wireless Remote Switch E1 (Single Rocker) + ["lumi.remote.b186acn03"] = { mfr = "LUMI", btn_cnt = 1, type = "CR2032", quantity = 1 }, -- Aqara Wireless Remote Switch T1 (Single Rocker) + ["lumi.remote.b286acn03"] = { mfr = "LUMI", btn_cnt = 3, type = "CR2032", quantity = 1 }, -- Aqara Wireless Remote Switch T1 (Double Rocker) + ["lumi.remote.b18ac1"] = { mfr = "LUMI", btn_cnt = 1, type = "CR2450", quantity = 1 }, -- Aqara Wireless Remote Switch H1 (Single Rocker) + ["lumi.remote.b28ac1"] = { mfr = "LUMI", btn_cnt = 3, type = "CR2450", quantity = 1 } -- Aqara Wireless Remote Switch H1 (Double Rocker) } local configuration = { - { - cluster = MULTISTATE_INPUT_CLUSTER_ID, - attribute = PRESENT_ATTRIBUTE_ID, - minimum_interval = 3, - maximum_interval = 7200, - data_type = data_types.Uint16, - reportable_change = 1 - }, - { - cluster = PowerConfiguration.ID, - attribute = PowerConfiguration.attributes.BatteryVoltage.ID, - minimum_interval = 30, - maximum_interval = 3600, - data_type = PowerConfiguration.attributes.BatteryVoltage.base_type, - reportable_change = 1 - } + { + cluster = MULTISTATE_INPUT_CLUSTER_ID, + attribute = PRESENT_ATTRIBUTE_ID, + minimum_interval = 3, + maximum_interval = 7200, + data_type = data_types.Uint16, + reportable_change = 1 + }, + { + cluster = PowerConfiguration.ID, + attribute = PowerConfiguration.attributes.BatteryVoltage.ID, + minimum_interval = 30, + maximum_interval = 3600, + data_type = PowerConfiguration.attributes.BatteryVoltage.base_type, + reportable_change = 1 + } } local function present_value_attr_handler(driver, device, value, zb_rx) + if value.value < 0xFF then + local end_point = zb_rx.address_header.src_endpoint.value + local btn_evt_cnt = AQARA_REMOTE_SWITCH[device:get_model()].btn_cnt or 1 + local evt = capabilities.button.button.held({ state_change = true }) if value.value == 1 then - device:emit_event(capabilities.button.button.pushed({state_change = true})) + evt = capabilities.button.button.pushed({ state_change = true }) elseif value.value == 2 then - device:emit_event(capabilities.button.button.double({state_change = true})) - elseif value.value == 0 then - device:emit_event(capabilities.button.button.held({state_change = true})) + evt = capabilities.button.button.double({ state_change = true }) + end + device:emit_event(evt) + if btn_evt_cnt > 1 then + device:emit_component_event(device.profile.components[COMP_LIST[end_point]], evt) end + end end -local is_aqara_products = function(opts, driver, device) - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true +local function calc_battery_percentage(voltage) + local millivolt = voltage * 100 + local percentage = 0 + if millivolt >= 3000 then + percentage = 100 + elseif millivolt >= 2600 then + local fVoltage = (millivolt * millivolt) * 0.00045; + percentage = fVoltage - 2.277 * millivolt + 2880 + end + + return math.floor(percentage) +end + +local function battery_level_handler(driver, device, value, zb_rx) + local voltage = value.value + local batteryLevel = "normal" + + if voltage <= 25 then + batteryLevel = "critical" + elseif voltage < 28 then + batteryLevel = "warning" + end + + if device:supports_capability_by_id(capabilities.battery.ID) then + device:emit_event(capabilities.battery.battery(calc_battery_percentage(voltage))) + elseif device:supports_capability_by_id(capabilities.batteryLevel.ID) then + device:emit_event(capabilities.batteryLevel.battery(batteryLevel)) + end +end + +local function mode_switching_handler(driver, device, value, zb_rx) + local btn_evt_cnt = AQARA_REMOTE_SWITCH[device:get_model()].btn_cnt or 1 + local allow = device.preferences[MODE_CHANGE] or false + if allow then + local mode = device:get_field(MODE) or 1 + mode = 3 - mode + device:set_field(MODE, mode, { persist = true }) + device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID_E1, + MFG_CODE, data_types.Uint8, mode)) + device:emit_event(capabilities.button.supportedButtonValues(SUPPORTED_BUTTON[mode], + { visibility = { displayed = false } })) + device:emit_event(capabilities.button.numberOfButtons({ value = 1 })) + button_utils.emit_event_if_latest_state_missing(device, "main", capabilities.button, capabilities.button.button.NAME, + capabilities.button.button.pushed({ state_change = false })) + if btn_evt_cnt > 1 then + for i = 1, btn_evt_cnt do + device:emit_component_event(device.profile.components[COMP_LIST[i]], + capabilities.button.supportedButtonValues(SUPPORTED_BUTTON[mode], + { visibility = { displayed = false } })) + device:emit_component_event(device.profile.components[COMP_LIST[i]], + capabilities.button.numberOfButtons({ value = 1 })) + device:emit_component_event(device.profile.components[COMP_LIST[i]], + capabilities.button.button.pushed({ state_change = false })) + button_utils.emit_event_if_latest_state_missing(device, COMP_LIST[i], capabilities.button, + capabilities.button.button.NAME, capabilities.button.button.pushed({ state_change = false })) end end - return false + end +end + +local is_aqara_products = function(opts, driver, device) + local isAqaraProducts = false + if AQARA_REMOTE_SWITCH[device:get_model()] and AQARA_REMOTE_SWITCH[device:get_model()].mfr == device:get_manufacturer() then + isAqaraProducts = true + end + return isAqaraProducts end local function device_init(driver, device) - battery_defaults.build_linear_voltage_init(2.6, 3.0)(driver, device) - if configuration ~= nil then - for _, attribute in ipairs(configuration) do - device:add_configured_attribute(attribute) - end + battery_defaults.build_linear_voltage_init(2.6, 3.0)(driver, device) + if configuration ~= nil then + for _, attribute in ipairs(configuration) do + device:add_configured_attribute(attribute) end + end end local function added_handler(self, device) - device:emit_event(capabilities.button.supportedButtonValues({"pushed","held","double"}, {visibility = { displayed = false }})) - device:emit_event(capabilities.button.numberOfButtons({value = 1})) - button_utils.emit_event_if_latest_state_missing(device, "main", capabilities.button, capabilities.button.button.NAME, capabilities.button.button.pushed({state_change = false})) - device:emit_event(capabilities.battery.battery(100)) + local btn_evt_cnt = AQARA_REMOTE_SWITCH[device:get_model()].btn_cnt or 1 + local mode = device:get_field(MODE) or 0 + local model = device:get_model() + local type = AQARA_REMOTE_SWITCH[device:get_model()].type or "CR2032" + local quantity = AQARA_REMOTE_SWITCH[device:get_model()].quantity or 1 + + if mode == 0 then + if model == "lumi.remote.b18ac1" or model == "lumi.remote.b28ac1" then + mode = 1 + else + mode = 2 + end + end + device:set_field(MODE, mode, { persist = true }) + device:emit_event(capabilities.button.supportedButtonValues(SUPPORTED_BUTTON[mode], + { visibility = { displayed = false } })) + device:emit_event(capabilities.button.numberOfButtons({ value = 1 })) + button_utils.emit_event_if_latest_state_missing(device, "main", capabilities.button, capabilities.button.button.NAME, + capabilities.button.button.pushed({ state_change = false })) + device:emit_event(capabilities.batteryLevel.battery.normal()) + device:emit_event(capabilities.batteryLevel.type(type)) + device:emit_event(capabilities.batteryLevel.quantity(quantity)) + + if btn_evt_cnt > 1 then + for i = 1, btn_evt_cnt do + device:emit_component_event(device.profile.components[COMP_LIST[i]], + capabilities.button.supportedButtonValues(SUPPORTED_BUTTON[mode], + { visibility = { displayed = false } })) + device:emit_component_event(device.profile.components[COMP_LIST[i]], + capabilities.button.numberOfButtons({ value = 1 })) + device:emit_component_event(device.profile.components[COMP_LIST[i]], + capabilities.button.button.pushed({ state_change = false })) + button_utils.emit_event_if_latest_state_missing(device, COMP_LIST[i], capabilities.button, + capabilities.button.button.NAME, capabilities.button.button.pushed({ state_change = false })) + end + end end local function do_configure(driver, device) + local ATTR_ID = PRIVATE_ATTRIBUTE_ID_T1 + local cmd_value = 1 + device:configure() - if device:get_model() == "lumi.remote.b1acn02" then - device:send(cluster_base.write_manufacturer_specific_attribute(device, - PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID_T1, MFG_CODE, data_types.Uint8, 1)) - elseif device:get_model() == "lumi.remote.acn003" then - device:send(cluster_base.write_manufacturer_specific_attribute(device, - PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID_E1, MFG_CODE, data_types.Uint8, 2)) + if device:get_model() == "lumi.remote.acn003" then + ATTR_ID = PRIVATE_ATTRIBUTE_ID_E1 + cmd_value = 2 end + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, ATTR_ID, MFG_CODE, data_types.Uint8, cmd_value)) -- when the wireless switch T1 accesses the network, the gateway sends -- private attribute 0009 to make the device no longer distinguish -- between the standard gateway and the aqara gateway. @@ -105,20 +214,26 @@ local function do_configure(driver, device) end local aqara_wireless_switch_handler = { - NAME = "Aqara Wireless Switch Handler", - lifecycle_handlers = { - init = device_init, - added = added_handler, - doConfigure = do_configure - }, - zigbee_handlers = { - attr = { - [MULTISTATE_INPUT_CLUSTER_ID] = { - [PRESENT_ATTRIBUTE_ID] = present_value_attr_handler - } + NAME = "Aqara Wireless Switch Handler", + lifecycle_handlers = { + init = device_init, + added = added_handler, + doConfigure = do_configure + }, + zigbee_handlers = { + attr = { + [MULTISTATE_INPUT_CLUSTER_ID] = { + [PRESENT_ATTRIBUTE_ID] = present_value_attr_handler + }, + [PowerConfiguration.ID] = { + [PowerConfiguration.attributes.BatteryVoltage.ID] = battery_level_handler + }, + [PRIVATE_CLUSTER_ID] = { + [PRIVATE_ATTRIBUTE_ID_ALIVE] = mode_switching_handler } - }, - can_handle = is_aqara_products + } + }, + can_handle = is_aqara_products } return aqara_wireless_switch_handler diff --git a/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua index b53a966761..838b9545fb 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua @@ -28,10 +28,13 @@ local MFG_CODE = 0x115F local PRIVATE_CLUSTER_ID = 0xFCC0 local PRIVATE_ATTRIBUTE_ID_T1 = 0x0009 local PRIVATE_ATTRIBUTE_ID_E1 = 0x0125 +local PRIVATE_ATTRIBUTE_ID_ALIVE = 0x00F7 +local MODE_CHANGE = "stse.allowOperationModeChange" +local COMP_LIST = { "button1", "button2", "all" } local mock_device_e1 = test.mock_device.build_test_zigbee_device( { - profile = t_utils.get_profile_definition("one-button-battery.yml"), + profile = t_utils.get_profile_definition("one-button-batteryLevel.yml"), zigbee_endpoints = { [1] = { id = 1, @@ -43,14 +46,14 @@ local mock_device_e1 = test.mock_device.build_test_zigbee_device( } ) -local mock_device_t1 = test.mock_device.build_test_zigbee_device( +local mock_device_h1_double_rocker = test.mock_device.build_test_zigbee_device( { - profile = t_utils.get_profile_definition("one-button-battery.yml"), + profile = t_utils.get_profile_definition("aqara-double-buttons-mode.yml"), zigbee_endpoints = { [1] = { id = 1, manufacturer = "LUMI", - model = "lumi.remote.b1acn02", + model = "lumi.remote.b286acn03", server_clusters = { 0x0001, 0x0012 } } } @@ -60,41 +63,35 @@ local mock_device_t1 = test.mock_device.build_test_zigbee_device( zigbee_test_utils.prepare_zigbee_env_info() local function test_init() test.mock_device.add_test_device(mock_device_e1) - test.mock_device.add_test_device(mock_device_t1)end + test.mock_device.add_test_device(mock_device_h1_double_rocker) +end test.set_test_init_function(test_init) test.register_coroutine_test( - "Handle added lifecycle -- e1", + "Handle added lifecycle - T1 double rocker", function() - -- The initial button pushed event should be send during the device's first time onboarding - test.socket.device_lifecycle:__queue_receive({ mock_device_e1.id, "added" }) - test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed","held","double"}, {visibility = { displayed = false }}))) - test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", capabilities.button.numberOfButtons({value = 1}))) - test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", capabilities.button.button.pushed({state_change = false}))) - test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", capabilities.battery.battery(100))) - -- Avoid sending the initial button pushed event after driver switch-over, as the switch-over event itself re-triggers the added lifecycle. - test.socket.device_lifecycle:__queue_receive({ mock_device_e1.id, "added" }) - test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed","held","double"}, {visibility = { displayed = false }}))) - test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", capabilities.button.numberOfButtons({value = 1}))) - test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", capabilities.battery.battery(100))) - end -) - -test.register_coroutine_test( - "Handle added lifecycle -- t1", - function() - -- The initial button pushed event should be send during the device's first time onboarding - test.socket.device_lifecycle:__queue_receive({ mock_device_t1.id, "added" }) - test.socket.capability:__expect_send(mock_device_t1:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed","held","double"}, {visibility = { displayed = false }}))) - test.socket.capability:__expect_send(mock_device_t1:generate_test_message("main", capabilities.button.numberOfButtons({value = 1}))) - test.socket.capability:__expect_send(mock_device_t1:generate_test_message("main", capabilities.button.button.pushed({state_change = false}))) - test.socket.capability:__expect_send(mock_device_t1:generate_test_message("main", capabilities.battery.battery(100))) - -- Avoid sending the initial button pushed event after driver switch-over, as the switch-over event itself re-triggers the added lifecycle. - test.socket.device_lifecycle:__queue_receive({ mock_device_t1.id, "added" }) - test.socket.capability:__expect_send(mock_device_t1:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed","held","double"}, {visibility = { displayed = false }}))) - test.socket.capability:__expect_send(mock_device_t1:generate_test_message("main", capabilities.button.numberOfButtons({value = 1}))) - test.socket.capability:__expect_send(mock_device_t1:generate_test_message("main", capabilities.battery.battery(100))) + test.socket.device_lifecycle:__queue_receive({ mock_device_h1_double_rocker.id, "added" }) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed", "held", "double" }, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 1 }))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("main", + capabilities.button.button.pushed({ state_change = false }))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("main", + capabilities.batteryLevel.battery.normal())) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("main", + capabilities.batteryLevel.type("CR2032"))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("main", + capabilities.batteryLevel.quantity(1))) + for i = 1, 3 do + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message(COMP_LIST[i], + capabilities.button.supportedButtonValues({ "pushed", "held", "double" }, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message(COMP_LIST[i], + capabilities.button.numberOfButtons({ value = 1 }))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message(COMP_LIST[i], + capabilities.button.button.pushed({ state_change = false }))) + end end ) @@ -116,12 +113,14 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device_e1.id, - zigbee_test_utils.build_attr_config(mock_device_e1, MULTISTATE_INPUT_CLUSTER_ID, PRESENT_ATTRIBUTE_ID, 0x0003, 0x1C20, data_types.Uint16, 0x0001) + zigbee_test_utils.build_attr_config(mock_device_e1, MULTISTATE_INPUT_CLUSTER_ID, PRESENT_ATTRIBUTE_ID, 0x0003, + 0x1C20, data_types.Uint16, 0x0001) }) test.socket.zigbee:__expect_send({ mock_device_e1.id, - cluster_base.write_manufacturer_specific_attribute(mock_device_e1, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID_E1, MFG_CODE, - data_types.Uint8, 2) }) - mock_device_e1:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + cluster_base.write_manufacturer_specific_attribute(mock_device_e1, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID_E1, + MFG_CODE, + data_types.Uint8, 2) }) + mock_device_e1:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end ) @@ -129,27 +128,31 @@ test.register_coroutine_test( test.register_coroutine_test( "Handle doConfigure lifecycle -- t1", function() - test.socket.device_lifecycle:__queue_receive({ mock_device_t1.id, "doConfigure" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_h1_double_rocker.id, "doConfigure" }) test.socket.zigbee:__expect_send({ - mock_device_t1.id, - zigbee_test_utils.build_bind_request(mock_device_t1, zigbee_test_utils.mock_hub_eui, PowerConfiguration.ID) + mock_device_h1_double_rocker.id, + zigbee_test_utils.build_bind_request(mock_device_h1_double_rocker, zigbee_test_utils.mock_hub_eui, + PowerConfiguration.ID) }) test.socket.zigbee:__expect_send({ - mock_device_t1.id, - PowerConfiguration.attributes.BatteryVoltage:configure_reporting(mock_device_t1, 30, 3600, 1) + mock_device_h1_double_rocker.id, + PowerConfiguration.attributes.BatteryVoltage:configure_reporting(mock_device_h1_double_rocker, 30, 3600, 1) }) test.socket.zigbee:__expect_send({ - mock_device_t1.id, - zigbee_test_utils.build_bind_request(mock_device_t1, zigbee_test_utils.mock_hub_eui, MULTISTATE_INPUT_CLUSTER_ID) + mock_device_h1_double_rocker.id, + zigbee_test_utils.build_bind_request(mock_device_h1_double_rocker, zigbee_test_utils.mock_hub_eui, + MULTISTATE_INPUT_CLUSTER_ID) }) test.socket.zigbee:__expect_send({ - mock_device_t1.id, - zigbee_test_utils.build_attr_config(mock_device_t1, MULTISTATE_INPUT_CLUSTER_ID, PRESENT_ATTRIBUTE_ID, 0x0003, 0x1C20, data_types.Uint16, 0x0001) + mock_device_h1_double_rocker.id, + zigbee_test_utils.build_attr_config(mock_device_h1_double_rocker, MULTISTATE_INPUT_CLUSTER_ID, PRESENT_ATTRIBUTE_ID, + 0x0003, 0x1C20, data_types.Uint16, 0x0001) }) - test.socket.zigbee:__expect_send({ mock_device_t1.id, - cluster_base.write_manufacturer_specific_attribute(mock_device_t1, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID_T1, MFG_CODE, - data_types.Uint8, 1) }) - mock_device_t1:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.zigbee:__expect_send({ mock_device_h1_double_rocker.id, + cluster_base.write_manufacturer_specific_attribute(mock_device_h1_double_rocker, PRIVATE_CLUSTER_ID, + PRIVATE_ATTRIBUTE_ID_T1, MFG_CODE, + data_types.Uint8, 1) }) + mock_device_h1_double_rocker:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end ) @@ -160,11 +163,14 @@ test.register_coroutine_test( { PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0001 } } test.socket.zigbee:__queue_receive({ - mock_device_e1.id, - zigbee_test_utils.build_attribute_report(mock_device_e1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data, MFG_CODE) + mock_device_h1_double_rocker.id, + zigbee_test_utils.build_attribute_report(mock_device_h1_double_rocker, MULTISTATE_INPUT_CLUSTER_ID, + attr_report_data, MFG_CODE) }) - test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", - capabilities.button.button.pushed({state_change = true}))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("main", + capabilities.button.button.pushed({ state_change = true }))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("button1", + capabilities.button.button.pushed({ state_change = true }))) end ) @@ -175,11 +181,14 @@ test.register_coroutine_test( { PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0002 } } test.socket.zigbee:__queue_receive({ - mock_device_e1.id, - zigbee_test_utils.build_attribute_report(mock_device_e1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data, MFG_CODE) + mock_device_h1_double_rocker.id, + zigbee_test_utils.build_attribute_report(mock_device_h1_double_rocker, MULTISTATE_INPUT_CLUSTER_ID, + attr_report_data, MFG_CODE) }) - test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", - capabilities.button.button.double({state_change = true}))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("main", + capabilities.button.button.double({ state_change = true }))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("button1", + capabilities.button.button.double({ state_change = true }))) end ) @@ -190,16 +199,19 @@ test.register_coroutine_test( { PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0000 } } test.socket.zigbee:__queue_receive({ - mock_device_e1.id, - zigbee_test_utils.build_attribute_report(mock_device_e1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data, MFG_CODE) + mock_device_h1_double_rocker.id, + zigbee_test_utils.build_attribute_report(mock_device_h1_double_rocker, MULTISTATE_INPUT_CLUSTER_ID, + attr_report_data, MFG_CODE) }) - test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", - capabilities.button.button.held({state_change = true}))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("main", + capabilities.button.button.held({ state_change = true }))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("button1", + capabilities.button.button.held({ state_change = true }))) end ) test.register_message_test( - "Battery voltage report should be handled", + "Battery Level - Normal", { { channel = "zigbee", @@ -209,9 +221,80 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_device_e1:generate_test_message("main", capabilities.battery.battery(100)) + message = mock_device_e1:generate_test_message("main", capabilities.batteryLevel.battery("normal")) + } + } +) +test.register_message_test( + "Battery Level - Warning", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device_e1.id, PowerConfiguration.attributes.BatteryVoltage:build_test_attr_report(mock_device_e1, 27) } + }, + { + channel = "capability", + direction = "send", + message = mock_device_e1:generate_test_message("main", capabilities.batteryLevel.battery("warning")) + } + } +) +test.register_message_test( + "Battery Level - Critical", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device_e1.id, PowerConfiguration.attributes.BatteryVoltage:build_test_attr_report(mock_device_e1, 20) } + }, + { + channel = "capability", + direction = "send", + message = mock_device_e1:generate_test_message("main", capabilities.batteryLevel.battery("critical")) } } ) +test.register_coroutine_test( + "Wireless Remote Switch H1 Mode Change", + function() + local mode = 2 + local updates = { + preferences = { + [MODE_CHANGE] = true + } + } + test.socket.device_lifecycle:__queue_receive(mock_device_h1_double_rocker:generate_info_changed(updates)) + mock_device_h1_double_rocker:set_field("devicemode", 1, { persist = true }) + local attr_report_data = { + { PRIVATE_ATTRIBUTE_ID_ALIVE, data_types.OctetString.ID, "\x01\x21\xB8\x0B\x03\x28\x19\x04\x21\xA8\x13\x05\x21\x45\x08\x06\x24\x07\x00\x00\x00\x00\x08\x21\x15\x01\x0A\x21\xF5\x65\x0C\x20\x01\x64\x20\x01\x66\x20\x03\x67\x20\x01\x68\x21\xA8\x00" } + } + test.wait_for_events() + test.socket.zigbee:__queue_receive({ + mock_device_h1_double_rocker.id, + zigbee_test_utils.build_attribute_report(mock_device_h1_double_rocker, PRIVATE_CLUSTER_ID, attr_report_data, + MFG_CODE) + }) + test.socket.zigbee:__expect_send({ mock_device_h1_double_rocker.id, cluster_base + .write_manufacturer_specific_attribute(mock_device_h1_double_rocker, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID_E1, + MFG_CODE, data_types.Uint8, mode) }) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed", "held", "double" }, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 1 }))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message("main", + capabilities.button.button.pushed({ state_change = false }))) + + for i = 1, 3 do + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message(COMP_LIST[i], + capabilities.button.supportedButtonValues({ "pushed", "held", "double" }, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message(COMP_LIST[i], + capabilities.button.numberOfButtons({ value = 1 }))) + test.socket.capability:__expect_send(mock_device_h1_double_rocker:generate_test_message(COMP_LIST[i], + capabilities.button.button.pushed({ state_change = false }))) + end + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/ClimaxTechnology/can_handle.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/ClimaxTechnology/can_handle.lua new file mode 100644 index 0000000000..95ad1a88fa --- /dev/null +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/ClimaxTechnology/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_climax_technology_carbon_monoxide = function(opts, driver, device) + local FINGERPRINTS = require("ClimaxTechnology.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("ClimaxTechnology") + end + end + + return false +end + +return is_climax_technology_carbon_monoxide diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/ClimaxTechnology/fingerprints.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/ClimaxTechnology/fingerprints.lua new file mode 100644 index 0000000000..d72389f772 --- /dev/null +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/ClimaxTechnology/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local CLIMAX_TECHNOLOGY_CARBON_MONOXIDE_FINGERPRINTS = { + { mfr = "ClimaxTechnology", model = "CO_00.00.00.22TC" }, + { mfr = "ClimaxTechnology", model = "CO_00.00.00.15TC" } +} + +return CLIMAX_TECHNOLOGY_CARBON_MONOXIDE_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/ClimaxTechnology/init.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/ClimaxTechnology/init.lua index cf96fb7162..1f18983c4b 100644 --- a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/ClimaxTechnology/init.lua +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/ClimaxTechnology/init.lua @@ -1,33 +1,10 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -local capabilities = require "st.capabilities" -local CLIMAX_TECHNOLOGY_CARBON_MONOXIDE_FINGERPRINTS = { - { mfr = "ClimaxTechnology", model = "CO_00.00.00.22TC" }, - { mfr = "ClimaxTechnology", model = "CO_00.00.00.15TC" } -} +local capabilities = require "st.capabilities" -local is_climax_technology_carbon_monoxide = function(opts, driver, device) - for _, fingerprint in ipairs(CLIMAX_TECHNOLOGY_CARBON_MONOXIDE_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local device_added = function(self, device) device:emit_event(capabilities.battery.battery(100)) @@ -38,7 +15,7 @@ local climax_technology_carbon_monoxide = { lifecycle_handlers = { added = device_added }, - can_handle = is_climax_technology_carbon_monoxide + can_handle = require("ClimaxTechnology.can_handle"), } return climax_technology_carbon_monoxide diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua index 21c170151e..8ef8a50795 100644 --- a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local ZigbeeDriver = require "st.zigbee" local capabilities = require "st.capabilities" @@ -24,8 +14,8 @@ local zigbee_carbon_monoxide_driver_template = { capabilities.battery, }, ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, - sub_drivers = { require("ClimaxTechnology") }, health_check = false, + sub_drivers = require("sub_drivers"), } defaults.register_for_default_handlers(zigbee_carbon_monoxide_driver_template, diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/lazy_load_subdriver.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(sub_driver_name) + -- gets the current lua libs api version + local version = require "version" + local ZigbeeDriver = require "st.zigbee" + if version.api >= 16 then + return ZigbeeDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZigbeeDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end +end diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/sub_drivers.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/sub_drivers.lua new file mode 100644 index 0000000000..6a7a185392 --- /dev/null +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/sub_drivers.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" + +local sub_drivers = { + lazy_load_if_possible("ClimaxTechnology") +} + +return sub_drivers diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_climax_technology_carbon_monoxide.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_climax_technology_carbon_monoxide.lua index a2ba57dfed..78164924d0 100644 --- a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_climax_technology_carbon_monoxide.lua +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_climax_technology_carbon_monoxide.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_zigbee_carbon_monoxide.lua b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_zigbee_carbon_monoxide.lua index 313f3ea9ec..07933958de 100644 --- a/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_zigbee_carbon_monoxide.lua +++ b/drivers/SmartThings/zigbee-carbon-monoxide-detector/src/test/test_zigbee_carbon_monoxide.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/init.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/init.lua index 9e45b9dbad..be7f8ad147 100644 --- a/drivers/SmartThings/zigbee-dimmer-remote/src/init.lua +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local ZigbeeDriver = require "st.zigbee" @@ -18,12 +8,12 @@ local defaults = require "st.zigbee.defaults" local zcl_clusters = require "st.zigbee.zcl.clusters" local battery_attribute_configuration = { - cluster = zcl_clusters.PowerConfiguration.ID, - attribute = zcl_clusters.PowerConfiguration.attributes.BatteryPercentageRemaining.ID, - minimum_interval = 30, - maximum_interval = 14300, -- ~4hrs - data_type = zcl_clusters.PowerConfiguration.attributes.BatteryPercentageRemaining.base_type, - reportable_change = 1 + cluster = zcl_clusters.PowerConfiguration.ID, + attribute = zcl_clusters.PowerConfiguration.attributes.BatteryPercentageRemaining.ID, + minimum_interval = 30, + maximum_interval = 14300, -- ~4hrs + data_type = zcl_clusters.PowerConfiguration.attributes.BatteryPercentageRemaining.base_type, + reportable_change = 1 } local function device_init(driver, device) @@ -38,9 +28,9 @@ local zigbee_dimmer_remote_driver_template = { capabilities.switchLevel }, lifecycle_handlers = { - init = device_init, + init = device_init, }, - sub_drivers = { require("zigbee-accessory-dimmer"), require("zigbee-battery-accessory-dimmer")}, + sub_drivers = require("sub_drivers"), health_check = false, } diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/lazy_load_subdriver.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(sub_driver_name) + -- gets the current lua libs api version + local version = require "version" + local ZigbeeDriver = require "st.zigbee" + if version.api >= 16 then + return ZigbeeDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZigbeeDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end +end diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/sub_drivers.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/sub_drivers.lua new file mode 100644 index 0000000000..c489a2b5d2 --- /dev/null +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/sub_drivers.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" + +local sub_drivers = { + lazy_load_if_possible("zigbee-accessory-dimmer"), + lazy_load_if_possible("zigbee-battery-accessory-dimmer"), +} + +return sub_drivers diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_accessory_dimmer.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_accessory_dimmer.lua index 477b2eac71..90d29656d8 100644 --- a/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_accessory_dimmer.lua +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_accessory_dimmer.lua @@ -1,4 +1,4 @@ --- Copyright 2022 SmartThings +-- Copyright 2022 SmartThings, Inc. -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-accessory-dimmer/can_handle.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-accessory-dimmer/can_handle.lua new file mode 100644 index 0000000000..6e87ef0c4b --- /dev/null +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-accessory-dimmer/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_accessory_dimmer = function(opts, driver, device) + local FINGERPRINTS = require("zigbee-accessory-dimmer.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-accessory-dimmer") + end + end + return false +end + +return is_zigbee_accessory_dimmer diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-accessory-dimmer/fingerprints.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-accessory-dimmer/fingerprints.lua new file mode 100644 index 0000000000..6528171f35 --- /dev/null +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-accessory-dimmer/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_ACCESSORY_DIMMER_FINGERPRINTS = { + { mfr = "Aurora", model = "Remote50AU" }, + { mfr = "LDS", model = "ZBT-DIMController-D0800" } +} + +return ZIGBEE_ACCESSORY_DIMMER_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-accessory-dimmer/init.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-accessory-dimmer/init.lua index 91662454c9..b37298802a 100644 --- a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-accessory-dimmer/init.lua +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-accessory-dimmer/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local device_management = require "st.zigbee.device_management" local zcl_clusters = require "st.zigbee.zcl.clusters" @@ -29,10 +19,6 @@ local CURRENT_LEVEL = "current_level" local CURRENT_STATUS = "current_status" local STEP = 10 -local ZIGBEE_ACCESSORY_DIMMER_FINGERPRINTS = { - { mfr = "Aurora", model = "Remote50AU" }, - { mfr = "LDS", model = "ZBT-DIMController-D0800" } -} local generate_switch_onoff_event = function(device, value) if value == "on" then @@ -142,14 +128,6 @@ local do_configure = function(self, device) end -local is_zigbee_accessory_dimmer = function(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_ACCESSORY_DIMMER_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local zigbee_accessory_dimmer = { NAME = "zigbee accessory dimmer", @@ -182,7 +160,7 @@ local zigbee_accessory_dimmer = { added = device_added, doConfigure = do_configure }, - can_handle = is_zigbee_accessory_dimmer + can_handle = require("zigbee-accessory-dimmer.can_handle"), } return zigbee_accessory_dimmer diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/can_handle.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/can_handle.lua new file mode 100644 index 0000000000..c3dd2870d4 --- /dev/null +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_centralite_systems = function(opts, driver, device) + local FINGERPRINTS = require("zigbee-battery-accessory-dimmer.CentraliteSystems.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-battery-accessory-dimmer.CentraliteSystems") + end + end + + return false +end + +return is_centralite_systems diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/fingerprints.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/fingerprints.lua new file mode 100644 index 0000000000..a8d90850b9 --- /dev/null +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local CENTRALITE_SYSTEMS_FINGERPRINTS = { + { mfr = "Centralite Systems", model = "3131-G" } +} + +return CENTRALITE_SYSTEMS_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/init.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/init.lua index 77bc9fc5a6..a7df73aec5 100644 --- a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/init.lua +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local device_management = require "st.zigbee.device_management" local battery_defaults = require "st.zigbee.defaults.battery_defaults" @@ -24,9 +14,6 @@ local capabilities = require "st.capabilities" local DEFAULT_LEVEL = 100 local DOUBLE_STEP = 10 -local CENTRALITE_SYSTEMS_FINGERPRINTS = { - { mfr = "Centralite Systems", model = "3131-G" } -} local generate_switch_level_event = function(device, value) device:emit_event(capabilities.switchLevel.level(value)) @@ -77,15 +64,6 @@ local do_configure = function(self, device) device:send(device_management.build_bind_request(device, Level.ID, self.environment_info.hub_zigbee_eui)) end -local is_centralite_systems = function(opts, driver, device) - for _, fingerprint in ipairs(CENTRALITE_SYSTEMS_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - - return false -end local voltage_configuration = { cluster = zcl_clusters.PowerConfiguration.ID, @@ -118,7 +96,7 @@ local centralite_systems = { init = device_init, doConfigure = do_configure }, - can_handle = is_centralite_systems + can_handle = require("zigbee-battery-accessory-dimmer.CentraliteSystems.can_handle"), } return centralite_systems diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/IKEAofSweden/can_handle.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/IKEAofSweden/can_handle.lua new file mode 100644 index 0000000000..888e889e6d --- /dev/null +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/IKEAofSweden/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_ikea_of_sweden = function(opts, driver, device) + local FINGERPRINTS = require("zigbee-battery-accessory-dimmer.IKEAofSweden.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-battery-accessory-dimmer.IKEAofSweden") + end + end + + return false +end + +return is_ikea_of_sweden diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/IKEAofSweden/fingerprints.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/IKEAofSweden/fingerprints.lua new file mode 100644 index 0000000000..429684c8c7 --- /dev/null +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/IKEAofSweden/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local IKEA_OF_SWEDEN_FINGERPRINTS = { + { mfr = "IKEA of Sweden", model = "TRADFRI wireless dimmer" } +} + +return IKEA_OF_SWEDEN_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/IKEAofSweden/init.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/IKEAofSweden/init.lua index 8edf50b296..4c87146ea2 100644 --- a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/IKEAofSweden/init.lua +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/IKEAofSweden/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local utils = require 'st.utils' local zcl_clusters = require "st.zigbee.zcl.clusters" @@ -23,9 +13,6 @@ local capabilities = require "st.capabilities" local DEFAULT_LEVEL = 100 local DOUBLE_STEP = 10 -local IKEA_OF_SWEDEN_FINGERPRINTS = { - { mfr = "IKEA of Sweden", model = "TRADFRI wireless dimmer" } -} local generate_switch_level_event = function(device, value) device:emit_event(capabilities.switchLevel.level(value)) @@ -99,15 +86,6 @@ local battery_perc_attr_handler = function(driver, device, value, zb_rx) device:emit_event(capabilities.battery.battery(utils.clamp_value(value.value, 0, 100))) end -local is_ikea_of_sweden = function(opts, driver, device) - for _, fingerprint in ipairs(IKEA_OF_SWEDEN_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - - return false -end local ikea_of_sweden = { NAME = "IKEA of Sweden", @@ -126,7 +104,7 @@ local ikea_of_sweden = { } } }, - can_handle = is_ikea_of_sweden + can_handle = require("zigbee-battery-accessory-dimmer.IKEAofSweden.can_handle"), } return ikea_of_sweden diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/can_handle.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/can_handle.lua new file mode 100644 index 0000000000..7b8f11b278 --- /dev/null +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_battery_accessory_dimmer = function(opts, driver, device) + local FINGERPRINTS = require("zigbee-battery-accessory-dimmer.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-battery-accessory-dimmer") + end + end + + return false +end + +return is_zigbee_battery_accessory_dimmer diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/fingerprints.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/fingerprints.lua new file mode 100644 index 0000000000..16c4a7f7b4 --- /dev/null +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_BATTERY_ACCESSORY_DIMMER_FINGERPRINTS = { + { mfr = "sengled", model = "E1E-G7F" }, + { mfr = "IKEA of Sweden", model = "TRADFRI wireless dimmer" }, + { mfr = "Centralite Systems", model = "3131-G" } +} + +return ZIGBEE_BATTERY_ACCESSORY_DIMMER_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/init.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/init.lua index 134931c2c8..01417c1a64 100644 --- a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/init.lua +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/init.lua @@ -1,16 +1,7 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local zcl_clusters = require "st.zigbee.zcl.clusters" local OnOff = zcl_clusters.OnOff @@ -22,11 +13,6 @@ local SwitchLevel = capabilities.switchLevel local DEFAULT_LEVEL = 100 local DOUBLE_STEP = 10 -local ZIGBEE_BATTERY_ACCESSORY_DIMMER_FINGERPRINTS = { - { mfr = "sengled", model = "E1E-G7F" }, - { mfr = "IKEA of Sweden", model = "TRADFRI wireless dimmer" }, - { mfr = "Centralite Systems", model = "3131-G" } -} local generate_switch_level_event = function(device, value) device:emit_event(capabilities.switchLevel.level(value)) @@ -89,15 +75,6 @@ local device_added = function(self, device) end end -local is_zigbee_battery_accessory_dimmer = function(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_BATTERY_ACCESSORY_DIMMER_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - - return false -end local zigbee_battery_accessory_dimmer = { NAME = "zigbee battery accessory dimmer", @@ -121,8 +98,7 @@ local zigbee_battery_accessory_dimmer = { lifecycle_handlers = { added = device_added }, - sub_drivers = { require("zigbee-battery-accessory-dimmer/CentraliteSystems"), require("zigbee-battery-accessory-dimmer/IKEAofSweden"), require("zigbee-battery-accessory-dimmer/sengled") }, - can_handle = is_zigbee_battery_accessory_dimmer + sub_drivers = require("zigbee-battery-accessory-dimmer.sub_drivers"), } return zigbee_battery_accessory_dimmer diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sengled/can_handle.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sengled/can_handle.lua new file mode 100644 index 0000000000..5fa64577ca --- /dev/null +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sengled/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_sengled = function(opts, driver, device) + local FINGERPRINTS = require("zigbee-battery-accessory-dimmer.sengled.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-battery-accessory-dimmer.sengled") + end + end + + return false +end + +return is_sengled diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sengled/fingerprints.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sengled/fingerprints.lua new file mode 100644 index 0000000000..252c48b6d4 --- /dev/null +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sengled/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local SENGLED_FINGERPRINTS = { + { mfr = "sengled", model = "E1E-G7F" } +} + +return SENGLED_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sengled/init.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sengled/init.lua index 08d3dfc4a8..7877c41b68 100644 --- a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sengled/init.lua +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sengled/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" @@ -20,9 +10,6 @@ local DOUBLE_STEP = 10 local SENGLED_MFR_SPECIFIC_CLUSTER = 0xFC10 local SENGLED_MFR_SPECIFIC_COMMAND = 0x00 -local SENGLED_FINGERPRINTS = { - { mfr = "sengled", model = "E1E-G7F" } -} local generate_switch_level_event = function(device, value) device:emit_event(capabilities.switchLevel.level(value)) @@ -84,15 +71,6 @@ local sengled_mfr_specific_command_handler = function(driver, device, zb_rx) end end -local is_sengled = function(opts, driver, device) - for _, fingerprint in ipairs(SENGLED_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - - return false -end local sengled = { NAME = "sengled", @@ -103,7 +81,7 @@ local sengled = { } } }, - can_handle = is_sengled + can_handle = require("zigbee-battery-accessory-dimmer.sengled.can_handle"), } return sengled diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sub_drivers.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sub_drivers.lua new file mode 100644 index 0000000000..0669f18b25 --- /dev/null +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/sub_drivers.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" + +local sub_drivers = { + lazy_load_if_possible("zigbee-battery-accessory-dimmer.CentraliteSystems"), + lazy_load_if_possible("zigbee-battery-accessory-dimmer.IKEAofSweden"), + lazy_load_if_possible("zigbee-battery-accessory-dimmer.sengled"), +} + +return sub_drivers diff --git a/drivers/SmartThings/zigbee-fan/src/fan-light/init.lua b/drivers/SmartThings/zigbee-fan/src/fan-light/init.lua index 95329c1ddf..3ec72ddb8f 100644 --- a/drivers/SmartThings/zigbee-fan/src/fan-light/init.lua +++ b/drivers/SmartThings/zigbee-fan/src/fan-light/init.lua @@ -74,7 +74,11 @@ local function zb_fan_control_handler(driver, device, value, zb_rx) end local function zb_level_handler(driver, device, value, zb_rx) - local evt = capabilities.switchLevel.level(math.floor((value.value / 254.0 * 100) + 0.5)) + local level = value.value + if level > 0 then + level = math.max(1, math.floor((level / 254.0 * 100) + 0.5)) + end + local evt = capabilities.switchLevel.level(level) device:emit_component_event(device.profile.components.light, evt) end diff --git a/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml b/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml index 5096f9cb25..68359710fe 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml +++ b/drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml @@ -58,6 +58,11 @@ zigbeeManufacturer: manufacturer: HEIMAN model: HT-EF-3.0 deviceProfileName: humidity-temp-battery + - id: frient/AQSZB-110 + deviceLabel: Air Quality Sensor + manufacturer: frient A/S + model: AQSZB-110 + deviceProfileName: frient-airquality-humidity-temperature-battery - id: frient/HMSZB-110 deviceLabel: frient Humidity Sensor manufacturer: frient A/S diff --git a/drivers/SmartThings/zigbee-humidity-sensor/profiles/frient-airquality-humidity-temperature-battery.yml b/drivers/SmartThings/zigbee-humidity-sensor/profiles/frient-airquality-humidity-temperature-battery.yml new file mode 100644 index 0000000000..08774c0d31 --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/profiles/frient-airquality-humidity-temperature-battery.yml @@ -0,0 +1,52 @@ +name: frient-airquality-humidity-temperature-battery +components: +- id: main + capabilities: + - id: airQualitySensor + version: 1 + - id: tvocMeasurement + version: 1 + - id: tvocHealthConcern + version: 1 + config: + values: + - key: "tvocHealthConcern.value" + enabledValues: + - good + - moderate + - slightlyUnhealthy + - unhealthy + - veryUnhealthy + - id: relativeHumidityMeasurement + version: 1 + - id: temperatureMeasurement + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 +preferences: + - preferenceId: humidityOffset + explicit: true + - title: "Humidity Sensitivity (%)" + name: humiditySensitivity + description: "Minimum change in humidity level to report" + required: false + preferenceType: number + definition: + minimum: 1 + maximum: 50 + default: 3 + - preferenceId: tempOffset + explicit: true + - title: "Temperature Sensitivity (°)" + name: temperatureSensitivity + description: "Minimum change in temperature to report" + required: false + preferenceType: number + definition: + minimum: 0.1 + maximum: 2.0 + default: 1.0 diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/aqara/can_handle.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/aqara/can_handle.lua new file mode 100644 index 0000000000..da7a97c64b --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/aqara/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_aqara_products = function(opts, driver, device) + local FINGERPRINTS = require("aqara.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("aqara") + end + end + return false +end + +return is_aqara_products diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/aqara/fingerprints.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/aqara/fingerprints.lua new file mode 100644 index 0000000000..8e6df9f7d6 --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/aqara/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FINGERPRINTS = { + { mfr = "LUMI", model = "lumi.sensor_ht.agl02" } +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/aqara/init.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/aqara/init.lua index 5256bf47e2..d3a8c7f89d 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/aqara/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" @@ -11,9 +14,6 @@ local PRIVATE_CLUSTER_ID = 0xFCC0 local PRIVATE_ATTRIBUTE_ID = 0x0009 local MFG_CODE = 0x115F -local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.sensor_ht.agl02" } -} -- temperature: 0.5C, humidity: 2% local configuration = { @@ -43,14 +43,6 @@ local configuration = { } } -local is_aqara_products = function(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 battery_level_handler(driver, device, value, zb_rx) local voltage = value.value @@ -98,7 +90,7 @@ local aqara_humidity_handler = { init = device_init, added = added_handler }, - can_handle = is_aqara_products + can_handle = require("aqara.can_handle"), } return aqara_humidity_handler diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/centralite-sensor/can_handle.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/centralite-sensor/can_handle.lua new file mode 100644 index 0000000000..a36cc01536 --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/centralite-sensor/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_centralite_sensor(opts, driver, device) + local FINGERPRINTS = require("centralite-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("centralite-sensor") + end + end + return false +end + +return can_handle_centralite_sensor diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/centralite-sensor/fingerprints.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/centralite-sensor/fingerprints.lua new file mode 100644 index 0000000000..df4f8a34a3 --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/centralite-sensor/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local CENTRALITE_SENSOR_FINGERPRINTS = { + { mfr = "CentraLite", model = "3310-S" }, + { mfr = "CentraLite", model = "3310-G" }, + { mfr = "CentraLite", model = "3310" } +} + +return CENTRALITE_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/centralite-sensor/init.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/centralite-sensor/init.lua index e1c11dd03b..11fedcfe24 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/centralite-sensor/init.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/centralite-sensor/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -23,20 +13,7 @@ local configurationMap = require "configurations" local HUMIDITY_CLUSTER_ID = 0xFC45 local HUMIDITY_MEASURE_ATTR_ID = 0x0000 -local CENTRALITE_SENSOR_FINGERPRINTS = { - { mfr = "CentraLite", model = "3310-S" }, - { mfr = "CentraLite", model = "3310-G" }, - { mfr = "CentraLite", model = "3310" } -} -local function can_handle_centralite_sensor(opts, driver, device) - for _, fingerprint in ipairs(CENTRALITE_SENSOR_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_init(driver, device) device:remove_configured_attribute(clusters.RelativeHumidity.ID, clusters.RelativeHumidity.attributes.MeasuredValue.ID) @@ -89,7 +66,7 @@ local centralite_sensor = { } } }, - can_handle = can_handle_centralite_sensor + can_handle = require("centralite-sensor.can_handle"), } return centralite_sensor diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/configurations.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/configurations.lua index 3a4e495e3e..8bc35c8765 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/configurations.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/configurations.lua @@ -1,4 +1,4 @@ --- Copyright 2022 SmartThings +-- Copyright 2022 SmartThings, Inc. -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. @@ -23,7 +23,8 @@ local devices = { FRIENT_HUMIDITY_TEMP_SENSOR = { FINGERPRINTS = { { mfr = "frient A/S", model = "HMSZB-110" }, - { mfr = "frient A/S", model = "HMSZB-120" } + { mfr = "frient A/S", model = "HMSZB-120" }, + { mfr = "frient A/S", model = "AQSZB-110" } }, CONFIGURATION = { { diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/air-quality/can_handle.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/air-quality/can_handle.lua new file mode 100644 index 0000000000..f82f572b43 --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/air-quality/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_frient(opts, driver, device, ...) + local FRIENT_AIR_QUALITY_SENSOR_FINGERPRINTS = require ("frient-sensor.air-quality.fingerprints") + for _, fingerprint in ipairs(FRIENT_AIR_QUALITY_SENSOR_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model and fingerprint.subdriver == "airquality" then + return true, require("frient-sensor.air-quality") + end + end + return false +end + +return can_handle_frient diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/air-quality/fingerprints.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/air-quality/fingerprints.lua new file mode 100644 index 0000000000..82c98c39fe --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/air-quality/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FRIENT_AIR_QUALITY_SENSOR_FINGERPRINTS = { + { mfr = "frient A/S", model = "AQSZB-110", subdriver = "airquality" } +} + +return FRIENT_AIR_QUALITY_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/air-quality/init.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/air-quality/init.lua new file mode 100644 index 0000000000..25ff436988 --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/air-quality/init.lua @@ -0,0 +1,147 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local util = require "st.utils" +local data_types = require "st.zigbee.data_types" +local zcl_clusters = require "st.zigbee.zcl.clusters" +local TemperatureMeasurement = zcl_clusters.TemperatureMeasurement +local HumidityMeasurement = zcl_clusters.RelativeHumidity +local PowerConfiguration = zcl_clusters.PowerConfiguration +local device_management = require "st.zigbee.device_management" +local cluster_base = require "st.zigbee.cluster_base" +local battery_defaults = require "st.zigbee.defaults.battery_defaults" + +local Frient_VOCMeasurement = { + ID = 0xFC03, + ManufacturerSpecificCode = 0x1015, + attributes = { + MeasuredValue = { ID = 0x0000, base_type = data_types.Uint16 }, + MinMeasuredValue = { ID = 0x0001, base_type = data_types.Uint16 }, + MaxMeasuredValue = { ID = 0x0002, base_type = data_types.Uint16 }, + Resolution = { ID = 0x0003, base_type = data_types.Uint16 }, + }, +} + +Frient_VOCMeasurement.attributes.MeasuredValue._cluster = Frient_VOCMeasurement +Frient_VOCMeasurement.attributes.MinMeasuredValue._cluster = Frient_VOCMeasurement +Frient_VOCMeasurement.attributes.MaxMeasuredValue._cluster = Frient_VOCMeasurement +Frient_VOCMeasurement.attributes.Resolution._cluster = Frient_VOCMeasurement + +local MAX_VOC_REPORTABLE_VALUE = 5500 -- Max VOC reportable value + +--- Table to map VOC (ppb) to HealthConcern +local VOC_TO_HEALTHCONCERN_MAPPING = { + [2201] = "veryUnhealthy", + [661] = "unhealthy", + [221] = "slightlyUnhealthy", + [66] = "moderate", + [0] = "good", +} + +--- Map VOC (ppb) to HealthConcern +local function voc_to_healthconcern(raw_voc) + for voc, perc in util.rkeys(VOC_TO_HEALTHCONCERN_MAPPING) do + if raw_voc >= voc then + return perc + end + end +end +--- Map VOC (ppb) to CAQI +local function voc_to_caqi(raw_voc) + if (raw_voc > 5500) then + return 100 + else + return math.floor(raw_voc*99/5500) + end +end + +-- May take around 8 minutes for the first valid VOC measurement to be reported after the device is powered on +local function voc_measure_value_attr_handler(driver, device, attr_val, zb_rx) + local voc_value = attr_val.value + if (voc_value < 65535) then -- ignore it if it's outside the limits + voc_value = util.clamp_value(voc_value, 0, MAX_VOC_REPORTABLE_VALUE) + device:emit_event(capabilities.airQualitySensor.airQuality({ value = voc_to_caqi(voc_value)})) + device:emit_event(capabilities.tvocHealthConcern.tvocHealthConcern(voc_to_healthconcern(voc_value))) + device:emit_event(capabilities.tvocMeasurement.tvocLevel({ value = voc_value, unit = "ppb" })) + end +end + +-- The device sends the value of MeasuredValue to be 0x8000, which corresponds to -327.68C, until it gets the first valid measurement. Therefore we don't emit event before the value is correct. It may take up to 4 minutes +local function temperatureHandler(driver, device, attr_val, zb_rx) + local temp_value = attr_val.value + if (temp_value > -32768) then + device:emit_event(capabilities.temperatureMeasurement.temperature({ value = temp_value / 100, unit = "C" })) + end +end + +local function device_init(driver, device) + local configurationMap = require "configurations" + battery_defaults.build_linear_voltage_init(2.3, 3.0)(driver, device) + local configuration = configurationMap.get_device_configuration(device) + if configuration ~= nil then + for _, attribute in ipairs(configuration) do + device:add_configured_attribute(attribute) + end + end +end + +local function device_added(driver, device) + device:emit_event(capabilities.airQualitySensor.airQuality(voc_to_caqi(0))) + device:emit_event(capabilities.tvocHealthConcern.tvocHealthConcern(voc_to_healthconcern(0))) + device:emit_event(capabilities.tvocMeasurement.tvocLevel({ value = 0, unit = "ppb" })) +end + +local function do_refresh(driver, device) + local FRIENT_AIR_QUALITY_SENSOR_FINGERPRINTS = require "frient-sensor.air-quality.fingerprints" + for _, fingerprint in ipairs(FRIENT_AIR_QUALITY_SENSOR_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + device:send(cluster_base.read_manufacturer_specific_attribute(device, Frient_VOCMeasurement.ID, Frient_VOCMeasurement.attributes.MeasuredValue.ID, Frient_VOCMeasurement.ManufacturerSpecificCode):to_endpoint(0x26)) + device:send(TemperatureMeasurement.attributes.MeasuredValue:read(device):to_endpoint(0x26)) + device:send(HumidityMeasurement.attributes.MeasuredValue:read(device):to_endpoint(0x26)) + device:send(PowerConfiguration.attributes.BatteryVoltage:read(device)) + end + end +end + +local function do_configure(driver, device) + device:configure() + device:send(device_management.build_bind_request(device, Frient_VOCMeasurement.ID, driver.environment_info.hub_zigbee_eui, 0x26)) + + device:send( + cluster_base.configure_reporting( + device, + data_types.ClusterId(Frient_VOCMeasurement.ID), + Frient_VOCMeasurement.attributes.MeasuredValue.ID, + Frient_VOCMeasurement.attributes.MeasuredValue.base_type.ID, + 60, 600, 10 + ):to_endpoint(0x26) + ) + + device.thread:call_with_delay(5, function() + do_refresh(driver, device) + end) +end + +local frient_airquality_sensor = { + NAME = "frient Air Quality Sensor", + lifecycle_handlers = { + init = device_init, + added = device_added, + doConfigure = do_configure, + }, + zigbee_handlers = { + cluster = {}, + attr = { + [Frient_VOCMeasurement.ID] = { + [Frient_VOCMeasurement.attributes.MeasuredValue.ID] = voc_measure_value_attr_handler, + }, + [TemperatureMeasurement.ID] = { + [TemperatureMeasurement.attributes.MeasuredValue.ID] = temperatureHandler, + }, + } + }, + can_handle = require("frient-sensor.air-quality.can_handle") +} + +return frient_airquality_sensor diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/can_handle.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/can_handle.lua new file mode 100644 index 0000000000..060f58f76e --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_frient_sensor(opts, driver, device) + local FINGERPRINTS = require("frient-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("frient-sensor") + end + end + return false +end + +return can_handle_frient_sensor diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/fingerprints.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/fingerprints.lua new file mode 100644 index 0000000000..f7969cafdd --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FRIENT_TEMP_HUMUDITY_SENSOR_FINGERPRINTS = { + { mfr = "frient A/S", model = "HMSZB-110" }, + { mfr = "frient A/S", model = "HMSZB-120" }, + { mfr = "frient A/S", model = "AQSZB-110" } +} + +return FRIENT_TEMP_HUMUDITY_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/init.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/init.lua index 7791e6752b..d0d6190ad0 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/init.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/init.lua @@ -1,38 +1,13 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -local battery_defaults = require "st.zigbee.defaults.battery_defaults" -local configurationMap = require "configurations" local zcl_clusters = require "st.zigbee.zcl.clusters" local HumidityMeasurement = zcl_clusters.RelativeHumidity local TemperatureMeasurement = zcl_clusters.TemperatureMeasurement -local FRIENT_TEMP_HUMUDITY_SENSOR_FINGERPRINTS = { - { mfr = "frient A/S", model = "HMSZB-110" }, - { mfr = "frient A/S", model = "HMSZB-120" } -} - -local function can_handle_frient_sensor(opts, driver, device) - for _, fingerprint in ipairs(FRIENT_TEMP_HUMUDITY_SENSOR_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_init(driver, device) + local configurationMap = require "configurations" + local battery_defaults = require "st.zigbee.defaults.battery_defaults" battery_defaults.build_linear_voltage_init(2.3,3.0)(driver, device) local configuration = configurationMap.get_device_configuration(device) if configuration ~= nil then @@ -73,7 +48,8 @@ local frient_sensor = { doConfigure = do_configure, infoChanged = info_changed }, - can_handle = can_handle_frient_sensor + sub_drivers = require("frient-sensor.sub_drivers"), + can_handle = require("frient-sensor.can_handle"), } return frient_sensor diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/sub_drivers.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/sub_drivers.lua new file mode 100644 index 0000000000..cc45e1b394 --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/frient-sensor/sub_drivers.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" +local sub_drivers = { + lazy_load_if_possible("frient-sensor.air-quality") +} + +return sub_drivers diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/heiman-sensor/can_handle.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/heiman-sensor/can_handle.lua new file mode 100644 index 0000000000..4e3aff5517 --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/heiman-sensor/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_heiman_sensor(opts, driver, device) + local FINGERPRINTS = require("heiman-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("heiman-sensor") + end + end + return false +end + +return can_handle_heiman_sensor diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/heiman-sensor/fingerprints.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/heiman-sensor/fingerprints.lua new file mode 100644 index 0000000000..b0f9e1c7ab --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/heiman-sensor/fingerprints.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local HEIMAN_TEMP_HUMUDITY_SENSOR_FINGERPRINTS = { + { mfr = "Heiman", model = "b467083cfc864f5e826459e5d8ea6079" }, + { mfr = "HEIMAN", model = "888a434f3cfc47f29ec4a3a03e9fc442" }, + { mfr = "HEIMAN", model = "HT-EM" }, + { mfr = "HEIMAN", model = "HT-EF-3.0" } +} + +return HEIMAN_TEMP_HUMUDITY_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/heiman-sensor/init.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/heiman-sensor/init.lua index 10a60c8392..8be2857f70 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/heiman-sensor/init.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/heiman-sensor/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" @@ -20,21 +10,7 @@ local RelativeHumidity = clusters.RelativeHumidity local TemperatureMeasurement = clusters.TemperatureMeasurement local PowerConfiguration = clusters.PowerConfiguration -local HEIMAN_TEMP_HUMUDITY_SENSOR_FINGERPRINTS = { - { mfr = "Heiman", model = "b467083cfc864f5e826459e5d8ea6079" }, - { mfr = "HEIMAN", model = "888a434f3cfc47f29ec4a3a03e9fc442" }, - { mfr = "HEIMAN", model = "HT-EM" }, - { mfr = "HEIMAN", model = "HT-EF-3.0" } -} -local function can_handle_heiman_sensor(opts, driver, device) - for _, fingerprint in ipairs(HEIMAN_TEMP_HUMUDITY_SENSOR_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local function do_refresh(driver, device) device:send(RelativeHumidity.attributes.MeasuredValue:read(device):to_endpoint(0x02)) @@ -59,7 +35,7 @@ local heiman_sensor = { [capabilities.refresh.commands.refresh.NAME] = do_refresh, } }, - can_handle = can_handle_heiman_sensor + can_handle = require("heiman-sensor.can_handle"), } return heiman_sensor diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/init.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/init.lua index 83a14c70dc..9ca7cd734d 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local ZigbeeDriver = require "st.zigbee" @@ -78,14 +68,7 @@ local zigbee_humidity_driver = { init = device_init, added = added_handler, }, - sub_drivers = { - require("aqara"), - require("plant-link"), - require("plaid-systems"), - require("centralite-sensor"), - require("heiman-sensor"), - require("frient-sensor") - }, + sub_drivers = require("sub_drivers"), health_check = false, } diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/lazy_load_subdriver.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(sub_driver_name) + -- gets the current lua libs api version + local version = require "version" + local ZigbeeDriver = require "st.zigbee" + if version.api >= 16 then + return ZigbeeDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZigbeeDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end +end diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/plaid-systems/can_handle.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/plaid-systems/can_handle.lua new file mode 100644 index 0000000000..a11116318e --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/plaid-systems/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_plaid_systems_humidity_sensor = function(opts, driver, device) + local FINGERPRINTS = require("plaid-systems.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("plaid-systems") + end + end + + return false +end + +return is_zigbee_plaid_systems_humidity_sensor diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/plaid-systems/fingerprints.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/plaid-systems/fingerprints.lua new file mode 100644 index 0000000000..fbacec9269 --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/plaid-systems/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_HUMIDITY_SENSOR_FINGERPRINTS = { + { mfr = "PLAID SYSTEMS", model = "PS-SPRZMS-01" }, + { mfr = "PLAID SYSTEMS", model = "PS-SPRZMS-SLP1" }, + { mfr = "PLAID SYSTEMS", model = "PS-SPRZMS-SLP3" }, +} + +return ZIGBEE_HUMIDITY_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/plaid-systems/init.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/plaid-systems/init.lua index 2233aab2a3..093007adf9 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/plaid-systems/init.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/plaid-systems/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local device_management = require "st.zigbee.device_management" @@ -21,21 +11,7 @@ local PowerConfiguration = zcl_clusters.PowerConfiguration local RelativeHumidity = zcl_clusters.RelativeHumidity local TemperatureMeasurement = zcl_clusters.TemperatureMeasurement -local ZIGBEE_HUMIDITY_SENSOR_FINGERPRINTS = { - { mfr = "PLAID SYSTEMS", model = "PS-SPRZMS-01" }, - { mfr = "PLAID SYSTEMS", model = "PS-SPRZMS-SLP1" }, - { mfr = "PLAID SYSTEMS", model = "PS-SPRZMS-SLP3" }, -} -local is_zigbee_plaid_systems_humidity_sensor = function(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_HUMIDITY_SENSOR_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - - return false -end local battery_mains_voltage_attr_handler = function(driver, device, value, zb_rx) local min = 2500 @@ -87,7 +63,7 @@ local plaid_systems_humdity_sensor = { } } }, - can_handle = is_zigbee_plaid_systems_humidity_sensor + can_handle = require("plaid-systems.can_handle"), } return plaid_systems_humdity_sensor diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/plant-link/can_handle.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/plant-link/can_handle.lua new file mode 100644 index 0000000000..9dcbc3e407 --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/plant-link/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_plant_link_humidity_sensor = function(opts, driver, device) + local FINGERPRINTS = require("plant-link.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model and device:supports_server_cluster(fingerprint.cluster_id) then + return true, require("plant-link") + end + end + + return false +end + +return is_zigbee_plant_link_humidity_sensor diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/plant-link/fingerprints.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/plant-link/fingerprints.lua new file mode 100644 index 0000000000..3b5e5f648d --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/plant-link/fingerprints.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local zcl_clusters = require "st.zigbee.zcl.clusters" +local PLANT_LINK_MANUFACTURER_SPECIFIC_CLUSTER = 0xFC08 + +local ZIGBEE_HUMIDITY_SENSOR_FINGERPRINTS = { + { mfr = "", model = "", cluster_id = PLANT_LINK_MANUFACTURER_SPECIFIC_CLUSTER }, + { mfr = "", model = "", cluster_id = zcl_clusters.ElectricalMeasurement.ID } +} + +return ZIGBEE_HUMIDITY_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/plant-link/init.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/plant-link/init.lua index 0a2d7fb225..07ab4a095e 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/plant-link/init.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/plant-link/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local zcl_clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" @@ -18,12 +8,7 @@ local PowerConfiguration = zcl_clusters.PowerConfiguration local RelativeHumidity = zcl_clusters.RelativeHumidity local utils = require "st.utils" -local PLANT_LINK_MANUFACTURER_SPECIFIC_CLUSTER = 0xFC08 -local ZIGBEE_HUMIDITY_SENSOR_FINGERPRINTS = { - { mfr = "", model = "", cluster_id = PLANT_LINK_MANUFACTURER_SPECIFIC_CLUSTER }, - { mfr = "", model = "", cluster_id = zcl_clusters.ElectricalMeasurement.ID } -} local humidity_value_attr_handler = function(driver, device, value, zb_rx) -- adc reading of 0x1ec0 produces a plant fuel level near 0 @@ -44,15 +29,6 @@ local battery_mains_voltage_attr_handler = function(driver, device, value, zb_rx device:emit_event(capabilities.battery.battery(percent)) end -local is_zigbee_plant_link_humidity_sensor = function(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_HUMIDITY_SENSOR_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model and device:supports_server_cluster(fingerprint.cluster_id) then - return true - end - end - - return false -end local plant_link_humdity_sensor = { NAME = "PlantLink Soil Moisture Sensor", @@ -70,7 +46,7 @@ local plant_link_humdity_sensor = { } } }, - can_handle = is_zigbee_plant_link_humidity_sensor + can_handle = require("plant-link.can_handle"), } return plant_link_humdity_sensor diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/sub_drivers.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/sub_drivers.lua new file mode 100644 index 0000000000..6f49a5f18c --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/sub_drivers.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" +local sub_drivers = { + lazy_load_if_possible("aqara"), + lazy_load_if_possible("plant-link"), + lazy_load_if_possible("plaid-systems"), + lazy_load_if_possible("centralite-sensor"), + lazy_load_if_possible("heiman-sensor"), + lazy_load_if_possible("frient-sensor"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_aqara_sensor.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_aqara_sensor.lua index 8f414975a1..497395ac55 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_aqara_sensor.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_aqara_sensor.lua @@ -1,4 +1,4 @@ --- Copyright 2022 SmartThings +-- Copyright 2022 SmartThings, Inc. -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_centralite_sensor.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_centralite_sensor.lua index 626d704f2a..c9b469376c 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_centralite_sensor.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_centralite_sensor.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_ewelink_sensor.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_ewelink_sensor.lua index 17d9afd2a5..35f557e850 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_ewelink_sensor.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_ewelink_sensor.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_air_quality_sensor.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_air_quality_sensor.lua new file mode 100644 index 0000000000..e985dafce5 --- /dev/null +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_air_quality_sensor.lua @@ -0,0 +1,294 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local data_types = require "st.zigbee.data_types" +local cluster_base = require "st.zigbee.cluster_base" + +local PowerConfiguration = clusters.PowerConfiguration +local TemperatureMeasurement = clusters.TemperatureMeasurement +local HumidityMeasurement = clusters.RelativeHumidity + +local Frient_VOCMeasurement = { + ID = 0xFC03, + ManufacturerSpecificCode = 0x1015, + attributes = { + MeasuredValue = { ID = 0x0000, base_type = data_types.Uint16 }, + MinMeasuredValue = { ID = 0x0001, base_type = data_types.Uint16 }, + MaxMeasuredValue = { ID = 0x0002, base_type = data_types.Uint16 }, + Resolution = { ID = 0x0003, base_type = data_types.Uint16 }, + }, +} + +Frient_VOCMeasurement.attributes.MeasuredValue._cluster = Frient_VOCMeasurement +Frient_VOCMeasurement.attributes.MinMeasuredValue._cluster = Frient_VOCMeasurement +Frient_VOCMeasurement.attributes.MaxMeasuredValue._cluster = Frient_VOCMeasurement +Frient_VOCMeasurement.attributes.Resolution._cluster = Frient_VOCMeasurement + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("frient-airquality-humidity-temperature-battery.yml"), + zigbee_endpoints = { + [0x26] = { + id = 0x26, + manufacturer = "frient A/S", + model = "AQSZB-110", + server_clusters = {0x0001, 0x0402, 0x0405, 0xFC03} + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.mock_device.add_test_device(mock_device) +end + +test.set_test_init_function(test_init) + +test.register_message_test( + "Refresh should read all necessary attributes", + { + { + channel = "capability", + direction = "receive", + message = {mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + HumidityMeasurement.attributes.MeasuredValue:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) + } + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +test.register_message_test( + "Min battery voltage report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryVoltage:build_test_attr_report(mock_device, 23) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(0)) + } + } +) + +test.register_message_test( + "Max battery voltage report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryVoltage:build_test_attr_report(mock_device, 30) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) + } + } +) + +test.register_coroutine_test( + "Configure should configure all necessary attributes", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.zigbee:__set_channel_ordering("relaxed") + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request( + mock_device, + zigbee_test_utils.mock_hub_eui, + PowerConfiguration.ID + ) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:configure_reporting(mock_device, 30, 21600, 1) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request( + mock_device, + zigbee_test_utils.mock_hub_eui, + TemperatureMeasurement.ID + ) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 0x001E, 0x0E10, 100) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request( + mock_device, + zigbee_test_utils.mock_hub_eui, + HumidityMeasurement.ID + ) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + HumidityMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 60, 3600, 300) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request( + mock_device, + zigbee_test_utils.mock_hub_eui, + Frient_VOCMeasurement.ID, + 38 + ):to_endpoint(0x26) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.configure_reporting( + mock_device, + data_types.ClusterId(Frient_VOCMeasurement.ID), + Frient_VOCMeasurement.attributes.MeasuredValue.ID, + Frient_VOCMeasurement.attributes.MeasuredValue.base_type.ID, + 60, 600, 10 + ):to_endpoint(0x26) + }) + + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.register_message_test( + "Humidity report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_device.id, + HumidityMeasurement.attributes.MeasuredValue:build_test_attr_report(mock_device, 0x1950) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 65 })) + } + } +) + +test.register_message_test( + "Temperature report should be handled (C) for the temperature cluster", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:build_test_attr_report(mock_device, 2500) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" })) + } + } +) + +test.register_coroutine_test( + "info_changed to check for necessary preferences settings: Temperature Sensitivity", + function() + local updates = { + preferences = { + temperatureSensitivity = 0.9, + humiditySensitivity = 10 + } + } + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) + local temperatureSensitivity = math.floor(0.9 * 100 + 0.5) + test.socket.zigbee:__expect_send({ mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting( + mock_device, + 30, + 3600, + temperatureSensitivity + ) + }) + local humiditySensitivity = math.floor(10 * 100 + 0.5) + test.socket.zigbee:__expect_send({ mock_device.id, + HumidityMeasurement.attributes.MeasuredValue:configure_reporting( + mock_device, + 60, + 3600, + humiditySensitivity + ) + }) + test.wait_for_events() + end +) + +test.register_message_test( + "VOC measurement report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, cluster_base.build_test_attr_report(Frient_VOCMeasurement.attributes.MeasuredValue, mock_device, 300) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.airQualitySensor.airQuality({ value = 5 })) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tvocHealthConcern.tvocHealthConcern({ value = "slightlyUnhealthy" })) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tvocMeasurement.tvocLevel({ value = 300, unit = "ppb" })) + } + }, + { + inner_block_ordering = "relaxed" + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_sensor.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_sensor.lua index c2723d0edd..5b9ef77634 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_sensor.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_sensor.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" @@ -231,4 +220,4 @@ test.register_coroutine_test( end ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_heiman_sensor.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_heiman_sensor.lua index 3dd7f5c719..773bd3e9e4 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_heiman_sensor.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_heiman_sensor.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_battery_sensor.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_battery_sensor.lua index 62f6421fa7..f914f0a898 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_battery_sensor.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_battery_sensor.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_plaid_systems.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_plaid_systems.lua index d9745bb681..6bca6c4e1a 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_plaid_systems.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_plaid_systems.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local base64 = require "st.base64" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature.lua index f3e7831b1f..6de1dceb18 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local base64 = require "st.base64" diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature_battery.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature_battery.lua index 9afd1c3a1d..341cd4bc20 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature_battery.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature_battery.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local base64 = require "st.base64" diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature_sensor.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature_sensor.lua index d4f3c09a05..4282db0ffd 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature_sensor.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature_sensor.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-illuminance-sensor/src/init.lua b/drivers/SmartThings/zigbee-illuminance-sensor/src/init.lua index 45a3ade62b..635503c232 100644 --- a/drivers/SmartThings/zigbee-illuminance-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-illuminance-sensor/src/init.lua @@ -16,6 +16,11 @@ local capabilities = require "st.capabilities" local ZigbeeDriver = require "st.zigbee" local defaults = require "st.zigbee.defaults" +local do_configure = function(self, device) + device:configure() + device:refresh() +end + local zigbee_illuminance_driver = { supported_capabilities = { capabilities.illuminanceMeasurement, @@ -24,6 +29,9 @@ local zigbee_illuminance_driver = { sub_drivers = { require("aqara") }, + lifecycle_handlers = { + doConfigure = do_configure, + }, health_check = false, } diff --git a/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor.lua b/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor.lua index 02c3c63a55..1987964075 100644 --- a/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor.lua +++ b/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor.lua @@ -107,6 +107,8 @@ test.register_coroutine_test( zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, PowerConfiguration.ID) } ) + test.socket.zigbee:__expect_send({ mock_device.id, IlluminanceMeasurement.attributes.MeasuredValue:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) end ) diff --git a/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml b/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml index 28aaea8d33..e71ab0c5ac 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml +++ b/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml @@ -24,6 +24,11 @@ zigbeeManufacturer: manufacturer: eWeLink model: MSO1 deviceProfileName: motion-battery + - id: eWeLink/SNZB-03P + deviceLabel: Zigbee Motion Sensor + manufacturer: eWeLink + model: SNZB-03P + deviceProfileName: motion - id: "ORVIBO/895a2d80097f4ae2b2d40500d5e0" deviceLabel: Orvibo Motion Sensor manufacturer: ORVIBO diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/aqara/aqara_utils.lua b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/aqara_utils.lua index e48f8fde92..8a1b184693 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/aqara/aqara_utils.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/aqara_utils.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local aqara_utils = {} diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/aqara/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/can_handle.lua new file mode 100644 index 0000000000..da7a97c64b --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_aqara_products = function(opts, driver, device) + local FINGERPRINTS = require("aqara.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("aqara") + end + end + return false +end + +return is_aqara_products diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/aqara/fingerprints.lua b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/fingerprints.lua new file mode 100644 index 0000000000..705fa72746 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FINGERPRINTS = { + { mfr = "LUMI", model = "lumi.motion.ac02" }, + { mfr = "LUMI", model = "lumi.motion.agl02" }, + { mfr = "LUMI", model = "lumi.motion.agl04" } +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/aqara/high-precision-motion/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/high-precision-motion/can_handle.lua new file mode 100644 index 0000000000..02c2f313cb --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/high-precision-motion/can_handle.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + if device:get_model() == "lumi.motion.agl04" then + return true, require("aqara.high-precision-motion") + end + return false +end diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/aqara/high-precision-motion/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/high-precision-motion/init.lua index 8c70857bdb..d6c89e3e0c 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/aqara/high-precision-motion/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/high-precision-motion/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" @@ -118,9 +121,7 @@ local aqara_high_precision_motion_handler = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_model() == "lumi.motion.agl04" - end + can_handle = require("aqara.high-precision-motion.can_handle") } return aqara_high_precision_motion_handler diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/aqara/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/init.lua index 1746b21953..e4586d4b0f 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local zcl_commands = require "st.zigbee.zcl.global_commands" local clusters = require "st.zigbee.zcl.clusters" @@ -16,11 +19,6 @@ local FREQUENCY_ATTRIBUTE_ID = 0x0102 local MOTION_DETECTED_UINT32 = 65536 -local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.motion.ac02" }, - { mfr = "LUMI", model = "lumi.motion.agl02" }, - { mfr = "LUMI", model = "lumi.motion.agl04" } -} local CONFIGURATIONS = { { @@ -33,14 +31,6 @@ local CONFIGURATIONS = { } } -local is_aqara_products = function(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 motion_illuminance_attr_handler(driver, device, value, zb_rx) -- The low 16 bits for Illuminance @@ -123,10 +113,8 @@ local aqara_motion_handler = { } } }, - sub_drivers = { - require("aqara.high-precision-motion") - }, - can_handle = is_aqara_products + sub_drivers = require("aqara.sub_drivers"), + can_handle = require("aqara.can_handle"), } return aqara_motion_handler diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/aqara/sub_drivers.lua b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/sub_drivers.lua new file mode 100644 index 0000000000..a0e42d2085 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/sub_drivers.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load = require "lazy_load_subdriver" + +return { + lazy_load("aqara.high-precision-motion") +} diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/aurora/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/aurora/can_handle.lua new file mode 100644 index 0000000000..f58429a53c --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/aurora/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function aurora_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "Aurora" and device:get_model() == "MotionSensor51AU" then + return true, require("aurora") + end + return false +end + +return aurora_can_handle diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/aurora/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/aurora/init.lua index 060ed9d308..d08eb13c36 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/aurora/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/aurora/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local function added_handler(self, device) @@ -24,9 +14,7 @@ local aurora_motion_driver = { lifecycle_handlers = { added = added_handler, }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Aurora" and device:get_model() == "MotionSensor51AU" - end + can_handle = require("aurora.can_handle"), } return aurora_motion_driver diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/battery-voltage/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/battery-voltage/can_handle.lua new file mode 100644 index 0000000000..01bf292b1a --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/battery-voltage/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local can_handle_battery_voltage = function(opts, driver, device, ...) + local FINGERPRINTS = require("battery-voltage.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("battery-voltage") + end + end + return false +end + +return can_handle_battery_voltage diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/battery-voltage/fingerprints.lua b/drivers/SmartThings/zigbee-motion-sensor/src/battery-voltage/fingerprints.lua new file mode 100644 index 0000000000..4d3006c943 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/battery-voltage/fingerprints.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local DEVICES_REPORTING_BATTERY_VOLTAGE = { + { mfr = "Bosch", model = "RFPR-ZB" }, + { mfr = "Bosch", model = "RFDL-ZB-MS" }, + { mfr = "Ecolink", model = "PIRZB1-ECO" }, + { mfr = "ADUROLIGHT", model = "VMS_ADUROLIGHT" }, + { mfr = "AduroSmart Eria", model = "VMS_ADUROLIGHT" } +} + +return DEVICES_REPORTING_BATTERY_VOLTAGE diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/battery-voltage/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/battery-voltage/init.lua index 316b626afe..d031aee153 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/battery-voltage/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/battery-voltage/init.lua @@ -1,35 +1,8 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -local battery_defaults = require "st.zigbee.defaults.battery_defaults" - -local DEVICES_REPORTING_BATTERY_VOLTAGE = { - { mfr = "Bosch", model = "RFPR-ZB" }, - { mfr = "Bosch", model = "RFDL-ZB-MS" }, - { mfr = "Ecolink", model = "PIRZB1-ECO" }, - { mfr = "ADUROLIGHT", model = "VMS_ADUROLIGHT" }, - { mfr = "AduroSmart Eria", model = "VMS_ADUROLIGHT" } -} -local can_handle_battery_voltage = function(opts, driver, device, ...) - for _, fingerprint in ipairs(DEVICES_REPORTING_BATTERY_VOLTAGE) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end +local battery_defaults = require "st.zigbee.defaults.battery_defaults" local battery_voltage_motion = { @@ -37,7 +10,7 @@ local battery_voltage_motion = { lifecycle_handlers = { init = battery_defaults.build_linear_voltage_init(2.1, 3.0) }, - can_handle = can_handle_battery_voltage + can_handle = require("battery-voltage.can_handle"), } return battery_voltage_motion diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/centralite/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/centralite/can_handle.lua new file mode 100644 index 0000000000..750bcab752 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/centralite/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function centralite_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "CentraLite" then + return true, require("centralite") + end + return false +end + +return centralite_can_handle diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/centralite/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/centralite/init.lua index 6a40360224..e2492e7c78 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/centralite/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/centralite/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local battery_defaults = require "st.zigbee.defaults.battery_defaults" local zcl_clusters = require "st.zigbee.zcl.clusters" @@ -46,9 +36,7 @@ local centralite_handler = { lifecycle_handlers = { init = init_handler }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "CentraLite" - end + can_handle = require("centralite.can_handle"), } return centralite_handler diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/compacta/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/compacta/can_handle.lua new file mode 100644 index 0000000000..7946b15125 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/compacta/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function compacta_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "Compacta" then + return true, require("compacta") + end + return false +end + +return compacta_can_handle diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/compacta/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/compacta/init.lua index b303f6f8fd..85295b6391 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/compacta/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/compacta/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- ZCL local zcl_clusters = require "st.zigbee.zcl.clusters" @@ -43,9 +33,7 @@ local compacta_driver = { added = added_handler, doConfigure = do_configure }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Compacta" - end + can_handle = require("compacta.can_handle"), } return compacta_driver diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/frient/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/frient/can_handle.lua new file mode 100644 index 0000000000..236ec1f3e5 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/frient/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_frient_motion_sensor(opts, driver, device) + local FINGERPRINTS = require("frient.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("frient") + end + end + return false +end + +return can_handle_frient_motion_sensor diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/frient/fingerprints.lua b/drivers/SmartThings/zigbee-motion-sensor/src/frient/fingerprints.lua new file mode 100644 index 0000000000..ddf11bbdfe --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/frient/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FRIENT_DEVICE_FINGERPRINTS = { + { mfr = "frient A/S", model = "MOSZB-140"}, + { mfr = "frient A/S", model = "MOSZB-141"}, + { mfr = "frient A/S", model = "MOSZB-153"} +} + +return FRIENT_DEVICE_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/frient/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/frient/init.lua index c067b2fe56..901dbf2cf2 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/frient/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local battery_defaults = require "st.zigbee.defaults.battery_defaults" local capabilities = require "st.capabilities" @@ -35,20 +24,7 @@ local POWER_CONFIGURATION_ENDPOINT = 0x23 local TEMPERATURE_ENDPOINT = 0x26 local ILLUMINANCE_ENDPOINT = 0x27 -local FRIENT_DEVICE_FINGERPRINTS = { - { mfr = "frient A/S", model = "MOSZB-140"}, - { mfr = "frient A/S", model = "MOSZB-141"}, - { mfr = "frient A/S", model = "MOSZB-153"} -} -local function can_handle_frient_motion_sensor(opts, driver, device) - for _, fingerprint in ipairs(FRIENT_DEVICE_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local function occupancy_attr_handler(driver, device, occupancy, zb_rx) device:emit_event(occupancy.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) @@ -214,6 +190,6 @@ local frient_motion_driver = { [capabilities.refresh.commands.refresh.NAME] = do_refresh } }, - can_handle = can_handle_frient_motion_sensor + can_handle = require("frient.can_handle"), } -return frient_motion_driver \ No newline at end of file +return frient_motion_driver diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/gatorsystem/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/gatorsystem/can_handle.lua new file mode 100644 index 0000000000..4257bd7ff2 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/gatorsystem/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function gatorsystem_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "GatorSystem" and device:get_model() == "GSHW01" then + return true, require("gatorsystem") + end + return false +end + +return gatorsystem_can_handle diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/gatorsystem/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/gatorsystem/init.lua index 1e90a65e94..ee88254233 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/gatorsystem/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/gatorsystem/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local zcl_clusters = require "st.zigbee.zcl.clusters" @@ -84,9 +74,7 @@ local gator_handler = { added = device_added, doConfigure = do_configure }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "GatorSystem" and device:get_model() == "GSHW01" - end + can_handle = require("gatorsystem.can_handle"), } return gator_handler diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/ikea/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/ikea/can_handle.lua new file mode 100644 index 0000000000..7060fc8630 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/ikea/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_ikea_motion = function(opts, driver, device) + local FINGERPRINTS = require("ikea.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("ikea") + end + end + return false +end + +return is_ikea_motion diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/ikea/fingerprints.lua b/drivers/SmartThings/zigbee-motion-sensor/src/ikea/fingerprints.lua new file mode 100644 index 0000000000..27162e73cf --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/ikea/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local IKEA_MOTION_SENSOR_FINGERPRINTS = { + { mfr = "IKEA of Sweden", model = "TRADFRI motion sensor" } +} + +return IKEA_MOTION_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/ikea/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/ikea/init.lua index b2af5e1c88..b7cc733959 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/ikea/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/ikea/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local constants = require "st.zigbee.constants" local clusters = require "st.zigbee.zcl.clusters" @@ -26,9 +16,6 @@ local OnOff = clusters.OnOff local PowerConfiguration = clusters.PowerConfiguration local Groups = clusters.Groups -local IKEA_MOTION_SENSOR_FINGERPRINTS = { - { mfr = "IKEA of Sweden", model = "TRADFRI motion sensor" } -} local MOTION_RESET_TIMER = "motionResetTimer" local ENTRIES_READ = "ENTRIES_READ" @@ -48,14 +35,6 @@ local function on_with_timed_off_command_handler(driver, device, zb_rx) device:set_field(MOTION_RESET_TIMER, motion_reset_timer) end -local is_ikea_motion = function(opts, driver, device) - for _, fingerprint in ipairs(IKEA_MOTION_SENSOR_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local function zdo_binding_table_handler(driver, device, zb_rx) for _, binding_table in pairs(zb_rx.body.zdo_body.binding_table_entries) do @@ -141,7 +120,7 @@ local ikea_motion_sensor = { added = device_added, doConfigure = do_configure }, - can_handle = is_ikea_motion + can_handle = require("ikea.can_handle"), } return ikea_motion_sensor diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/init.lua index 69b069bf7b..11e9d94a85 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/init.lua @@ -1,22 +1,13 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local ZigbeeDriver = require "st.zigbee" local defaults = require "st.zigbee.defaults" local constants = require "st.zigbee.constants" local zcl_clusters = require "st.zigbee.zcl.clusters" +local lazy_load_if_possible = require "lazy_load_subdriver" local temperature_measurement_defaults = { MIN_TEMP = "MIN_TEMP", @@ -110,23 +101,23 @@ local zigbee_motion_driver = { added = added_handler }, sub_drivers = { - require("aqara"), - require("aurora"), - require("ikea"), - require("iris"), - require("gatorsystem"), - require("motion_timeout"), - require("nyce"), - require("zigbee-plugin-motion-sensor"), - require("compacta"), - require("frient"), - require("samjin"), - require("battery-voltage"), - require("centralite"), - require("smartthings"), - require("smartsense"), - require("thirdreality"), - require("sengled") + lazy_load_if_possible("aqara"), + lazy_load_if_possible("aurora"), + lazy_load_if_possible("ikea"), + lazy_load_if_possible("iris"), + lazy_load_if_possible("gatorsystem"), + lazy_load_if_possible("motion_timeout"), + lazy_load_if_possible("nyce"), + lazy_load_if_possible("zigbee-plugin-motion-sensor"), + lazy_load_if_possible("compacta"), + lazy_load_if_possible("frient"), + lazy_load_if_possible("samjin"), + lazy_load_if_possible("battery-voltage"), + lazy_load_if_possible("centralite"), + lazy_load_if_possible("smartthings"), + lazy_load_if_possible("smartsense"), + lazy_load_if_possible("thirdreality"), + lazy_load_if_possible("sengled"), }, additional_zcl_profiles = { [0xFC01] = true diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/iris/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/iris/can_handle.lua new file mode 100644 index 0000000000..6d86812e50 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/iris/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_iris_motion_sensor = function(opts, driver, device) + local FINGERPRINTS = require("iris.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("iris") + end + end + return false +end + +return is_zigbee_iris_motion_sensor diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/iris/fingerprints.lua b/drivers/SmartThings/zigbee-motion-sensor/src/iris/fingerprints.lua new file mode 100644 index 0000000000..2d4ebfc516 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/iris/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_IRIS_MOTION_SENSOR_FINGERPRINTS = { + { mfr = "iMagic by GreatStar", model = "1117-S" } +} + +return ZIGBEE_IRIS_MOTION_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/iris/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/iris/init.lua index 5a553b4325..f29c2772d3 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/iris/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/iris/init.lua @@ -1,23 +1,10 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local zcl_clusters = require "st.zigbee.zcl.clusters" local battery_defaults = require "st.zigbee.defaults.battery_defaults" -local ZIGBEE_IRIS_MOTION_SENSOR_FINGERPRINTS = { - { mfr = "iMagic by GreatStar", model = "1117-S" } -} -- TODO: the IAS Zone changes should be replaced after supporting functions are included in the lua libs local do_init = function(driver, device) @@ -26,21 +13,13 @@ local do_init = function(driver, device) device:remove_configured_attribute(zcl_clusters.IASZone.ID, zcl_clusters.IASZone.attributes.ZoneStatus.ID) end -local is_zigbee_iris_motion_sensor = function(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_IRIS_MOTION_SENSOR_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local iris_motion_handler = { NAME = "Iris Motion Handler", lifecycle_handlers = { init = do_init }, - can_handle = is_zigbee_iris_motion_sensor + can_handle = require("iris.can_handle"), } return iris_motion_handler diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-motion-sensor/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/lazy_load_subdriver.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(sub_driver_name) + -- gets the current lua libs api version + local version = require "version" + local ZigbeeDriver = require "st.zigbee" + if version.api >= 16 then + return ZigbeeDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZigbeeDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end +end diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/motion_timeout/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/motion_timeout/can_handle.lua new file mode 100644 index 0000000000..679675b85e --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/motion_timeout/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_motion_sensor = function(opts, driver, device) + local FINGERPRINTS = require("motion_timeout.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("motion_timeout") + end + end + return false +end + +return is_zigbee_motion_sensor diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/motion_timeout/fingerprints.lua b/drivers/SmartThings/zigbee-motion-sensor/src/motion_timeout/fingerprints.lua new file mode 100644 index 0000000000..a3ff0ec226 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/motion_timeout/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_MOTION_SENSOR_FINGERPRINTS = { + { mfr = "ORVIBO", model = "895a2d80097f4ae2b2d40500d5e03dcc", timeout = 20 }, + { mfr = "Megaman", model = "PS601/z1", timeout = 20 }, + { mfr = "HEIMAN", model = "PIRSensor-N", timeout = 20 } +} + +return ZIGBEE_MOTION_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/motion_timeout/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/motion_timeout/init.lua index 0fd0e7c070..fb6f34ece4 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/motion_timeout/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/motion_timeout/init.lua @@ -1,40 +1,18 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local zcl_clusters = require "st.zigbee.zcl.clusters" local IASZone = zcl_clusters.IASZone -local ZIGBEE_MOTION_SENSOR_FINGERPRINTS = { - { mfr = "ORVIBO", model = "895a2d80097f4ae2b2d40500d5e03dcc", timeout = 20 }, - { mfr = "Megaman", model = "PS601/z1", timeout = 20 }, - { mfr = "HEIMAN", model = "PIRSensor-N", timeout = 20 } -} -local is_zigbee_motion_sensor = function(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_MOTION_SENSOR_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local generate_event_from_zone_status = function(driver, device, zone_status, zigbee_message) + local FINGERPRINTS = require("motion_timeout.fingerprints") device:emit_event( (zone_status:is_alarm1_set() or zone_status:is_alarm2_set()) and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) - for _, fingerprint in ipairs(ZIGBEE_MOTION_SENSOR_FINGERPRINTS) do + for _, fingerprint in ipairs(FINGERPRINTS) do if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then device.thread:call_with_delay(fingerprint.timeout, function(d) device:emit_event(capabilities.motionSensor.motion.inactive()) @@ -66,7 +44,7 @@ local motion_timeout_handler = { } } }, - can_handle = is_zigbee_motion_sensor + can_handle = require("motion_timeout.can_handle"), } return motion_timeout_handler diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/nyce/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/nyce/can_handle.lua new file mode 100644 index 0000000000..5c603cfef2 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/nyce/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_nyce_motion_sensor = function(opts, driver, device) + local FINGERPRINTS = require("nyce.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("nyce") + end + end + return false +end + +return is_zigbee_nyce_motion_sensor diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/nyce/fingerprints.lua b/drivers/SmartThings/zigbee-motion-sensor/src/nyce/fingerprints.lua new file mode 100644 index 0000000000..ecb5193f99 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/nyce/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_NYCE_MOTION_SENSOR_FINGERPRINTS = { + { mfr = "NYCE", model = "3041" }, + { mfr = "NYCE", model = "3043" }, + { mfr = "NYCE", model = "3045" } +} + +return ZIGBEE_NYCE_MOTION_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/nyce/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/nyce/init.lua index 8a8196afa6..71efbf3e23 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/nyce/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/nyce/init.lua @@ -1,36 +1,13 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local zcl_clusters = require "st.zigbee.zcl.clusters" local battery_defaults = require "st.zigbee.defaults.battery_defaults" local OccupancySensing = zcl_clusters.OccupancySensing -local ZIGBEE_NYCE_MOTION_SENSOR_FINGERPRINTS = { - { mfr = "NYCE", model = "3041" }, - { mfr = "NYCE", model = "3043" }, - { mfr = "NYCE", model = "3045" } -} -local is_zigbee_nyce_motion_sensor = function(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_NYCE_MOTION_SENSOR_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local function occupancy_attr_handler(driver, device, occupancy, zb_rx) device:emit_event( @@ -49,7 +26,7 @@ local nyce_motion_handler = { } } }, - can_handle = is_zigbee_nyce_motion_sensor + can_handle = require("nyce.can_handle"), } return nyce_motion_handler diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/samjin/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/samjin/can_handle.lua new file mode 100644 index 0000000000..1ad7fe5f7a --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/samjin/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function samjin_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "Samjin" then + return true, require("samjin") + end + return false +end + +return samjin_can_handle diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/samjin/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/samjin/init.lua index 66fe8b4491..5d3ff95fd0 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/samjin/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/samjin/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- ZCL local zcl_clusters = require "st.zigbee.zcl.clusters" @@ -44,9 +34,7 @@ local samjin_driver = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Samjin" - end + can_handle = require("samjin.can_handle"), } return samjin_driver diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/sengled/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/sengled/can_handle.lua new file mode 100644 index 0000000000..68d774fb8f --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/sengled/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_sengled_products = function(opts, driver, device, ...) + local FINGERPRINTS = require("sengled.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("sengled") + end + end + return false +end + +return is_sengled_products diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/sengled/fingerprints.lua b/drivers/SmartThings/zigbee-motion-sensor/src/sengled/fingerprints.lua new file mode 100644 index 0000000000..bae9a89938 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/sengled/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FINGERPRINTS = { + { mfr = "sengled", model = "E1M-G7H" } +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/sengled/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/sengled/init.lua index 0e2575415f..5c2402f32e 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/sengled/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/sengled/init.lua @@ -1,12 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local clusters = require "st.zigbee.zcl.clusters" local battery_defaults = require "st.zigbee.defaults.battery_defaults" local IASZone = clusters.IASZone local PowerConfiguration = clusters.PowerConfiguration -local FINGERPRINTS = { - { mfr = "sengled", model = "E1M-G7H" } -} local CONFIGURATIONS = { { @@ -27,14 +27,6 @@ local CONFIGURATIONS = { } } -local is_sengled_products = function(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_init(driver, device) battery_defaults.build_linear_voltage_init(2.6, 3.0)(driver, device) @@ -49,7 +41,7 @@ local sengled_motion_sensor_handler = { lifecycle_handlers = { init = device_init }, - can_handle = is_sengled_products + can_handle = require("sengled.can_handle"), } return sengled_motion_sensor_handler diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/smartsense/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/smartsense/can_handle.lua new file mode 100644 index 0000000000..e85561680b --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/smartsense/can_handle.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle(opts, driver, device, ...) + + local SMARTSENSE_MFR = "SmartThings" + local SMARTSENSE_MODEL = "PGC314" + local SMARTSENSE_PROFILE_ID = 0xFC01 + + local endpoint = device.zigbee_endpoints[1] or device.zigbee_endpoints["1"] + if (device:get_manufacturer() == SMARTSENSE_MFR and device:get_model() == SMARTSENSE_MODEL) or + endpoint.profile_id == SMARTSENSE_PROFILE_ID then + return true, require("smartsense") + end + return false +end + +return can_handle diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/smartsense/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/smartsense/init.lua index 78f570872f..df48f6af95 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/smartsense/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/smartsense/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local utils = require "st.utils" @@ -18,9 +8,6 @@ local utils = require "st.utils" local motion = capabilities.motionSensor.motion local signalStrength = capabilities.signalStrength -local SMARTSENSE_MFR = "SmartThings" -local SMARTSENSE_MODEL = "PGC314" -local SMARTSENSE_PROFILE_ID = 0xFC01 local SMARTSENSE_MOTION_CLUSTER = 0xFC04 local SMARTSENSE_MOTION_STATUS_CMD = 0x00 local MOTION_MASK = 0x02 @@ -43,14 +30,6 @@ local battery_table = { [0] = 0 } -local function can_handle(opts, driver, device, ...) - local endpoint = device.zigbee_endpoints[1] or device.zigbee_endpoints["1"] - if (device:get_manufacturer() == SMARTSENSE_MFR and device:get_model() == SMARTSENSE_MODEL) or - endpoint.profile_id == SMARTSENSE_PROFILE_ID then - return true - end - return false -end local function device_added(driver, device) device:emit_event(motion.inactive()) @@ -96,7 +75,7 @@ local smartsense_motion = { lifecycle_handlers = { added = device_added }, - can_handle = can_handle + can_handle = require("smartsense.can_handle"), } return smartsense_motion diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/smartthings/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/smartthings/can_handle.lua new file mode 100644 index 0000000000..747f859062 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/smartthings/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function smartthings_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "SmartThings" then + return true, require("smartthings") + end + return false + end + +return smartthings_can_handle diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/smartthings/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/smartthings/init.lua index 586378edc5..0907e7de16 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/smartthings/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/smartthings/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local zcl_clusters = require "st.zigbee.zcl.clusters" local battery_defaults = require "st.zigbee.defaults.battery_defaults" @@ -44,9 +34,7 @@ local smartthings_motion = { lifecycle_handlers = { init = init_handler }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "SmartThings" - end + can_handle = require("smartthings.can_handle"), } return smartthings_motion diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/st/zigbee/zdo/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/st/zigbee/zdo/init.lua index 9d009d47cb..eb551c84fa 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/st/zigbee/zdo/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/st/zigbee/zdo/init.lua @@ -1,16 +1,6 @@ --- Copyright 2021 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. +-- Copyright 2021 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local data_types = require "st.zigbee.data_types" local utils = require "st.zigbee.utils" local zdo_commands = require "st.zigbee.zdo.commands" @@ -101,7 +91,7 @@ function ZdoMessageBody.deserialize(parent, buf) -- binding table entry endpoint_id local version = require "version" if version.rpc == 8 then - buf.buf = buf.buf .. "\01" + buf.buf = buf.buf .. "" buf:seek(1) end s.zdo_body = zdo_commands.parse_zdo_command(parent.address_header.cluster.value, buf) @@ -152,4 +142,4 @@ end setmetatable(zdo_messages.ZdoMessageBody, { __call = zdo_messages.ZdoMessageBody.from_values }) -return zdo_messages \ No newline at end of file +return zdo_messages diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_all_capabilities_zigbee_motion.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_all_capabilities_zigbee_motion.lua index b016503f44..948d433c9f 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_all_capabilities_zigbee_motion.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_all_capabilities_zigbee_motion.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aqara_high_precision.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aqara_high_precision.lua index 4ec5b9a3f3..e7bfcbf642 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aqara_high_precision.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aqara_high_precision.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aqara_motion_illuminance.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aqara_motion_illuminance.lua index 52b437f86e..2580af0ceb 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aqara_motion_illuminance.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aqara_motion_illuminance.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aurora_motion.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aurora_motion.lua index 200d8e544b..1a5a292fc3 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aurora_motion.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aurora_motion.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_battery_voltage_motion.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_battery_voltage_motion.lua index 5878d9e687..737a594406 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_battery_voltage_motion.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_battery_voltage_motion.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_centralite_motion.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_centralite_motion.lua index 2fafad786b..1e35976fac 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_centralite_motion.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_centralite_motion.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_compacta_motion.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_compacta_motion.lua index 2e50c1a615..0660661d80 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_compacta_motion.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_compacta_motion.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor.lua index 9b973b1724..89d014dc90 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local base64 = require "base64" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor2_pet.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor2_pet.lua index 0f142ea6cb..6869412d49 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor2_pet.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor2_pet.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local base64 = require "base64" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor_pro.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor_pro.lua index cda87474fa..9556a6e58e 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor_pro.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor_pro.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local base64 = require "base64" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_gator_motion.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_gator_motion.lua index 2ae1fe0952..7f47416e93 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_gator_motion.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_gator_motion.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_ikea_motion.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_ikea_motion.lua index 133fe63dbb..12e0038c6c 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_ikea_motion.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_ikea_motion.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_samjin_sensor.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_samjin_sensor.lua index d42685e704..d966d301f3 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_samjin_sensor.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_samjin_sensor.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_sengled_motion.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_sengled_motion.lua index da645dcc9f..4450761c5d 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_sengled_motion.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_sengled_motion.lua @@ -1,16 +1,6 @@ --- 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. +-- Copyright 2023 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_smartsense_motion_sensor.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_smartsense_motion_sensor.lua index b61abdec38..f96322467f 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_smartsense_motion_sensor.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_smartsense_motion_sensor.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local zigbee_test_utils = require "integration_test.zigbee_test_utils" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_smartthings_motion.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_smartthings_motion.lua index 5997a2f75c..5211ce1982 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_smartthings_motion.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_smartthings_motion.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_thirdreality_sensor.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_thirdreality_sensor.lua index 1113f34208..ddeaf77ce4 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_thirdreality_sensor.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_thirdreality_sensor.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_motion_iris.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_motion_iris.lua index 0e91d76d49..f0cd1053dc 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_motion_iris.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_motion_iris.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_motion_nyce.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_motion_nyce.lua index 614e971671..e368e852c8 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_motion_nyce.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_motion_nyce.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_motion_orvibo.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_motion_orvibo.lua index 25db204646..1f586bce88 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_motion_orvibo.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_motion_orvibo.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_plugin_motion_sensor.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_plugin_motion_sensor.lua index f957f8ce2f..78634bbaa2 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_plugin_motion_sensor.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_zigbee_plugin_motion_sensor.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/thirdreality/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/thirdreality/can_handle.lua new file mode 100644 index 0000000000..4f087c6e5d --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/thirdreality/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_third_reality_motion_sensor = function(opts, driver, device) + local FINGERPRINTS = require("thirdreality.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("thirdreality") + end + end + return false +end + +return is_third_reality_motion_sensor diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/thirdreality/fingerprints.lua b/drivers/SmartThings/zigbee-motion-sensor/src/thirdreality/fingerprints.lua new file mode 100644 index 0000000000..a5e92ef0b9 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/thirdreality/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_MOTION_SENSOR_FINGERPRINTS = { + { mfr = "Third Reality, Inc", model = "3RMS16BZ"}, + { mfr = "THIRDREALITY", model = "3RMS16BZ"} +} + +return ZIGBEE_MOTION_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/thirdreality/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/thirdreality/init.lua index c93919f16b..37c6e5b8d6 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/thirdreality/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/thirdreality/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local zcl_clusters = require "st.zigbee.zcl.clusters" @@ -20,19 +10,7 @@ local utils = require "st.utils" local APPLICATION_VERSION = "application_version" -local ZIGBEE_MOTION_SENSOR_FINGERPRINTS = { - { mfr = "Third Reality, Inc", model = "3RMS16BZ"}, - { mfr = "THIRDREALITY", model = "3RMS16BZ"} -} -local is_third_reality_motion_sensor = function(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_MOTION_SENSOR_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end local device_added = function(self, device) device:set_field(APPLICATION_VERSION, 0) @@ -73,7 +51,7 @@ local third_reality_motion_sensor = { lifecycle_handlers = { added = device_added, }, - can_handle = is_third_reality_motion_sensor + can_handle = require("thirdreality.can_handle"), } return third_reality_motion_sensor diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/zigbee-plugin-motion-sensor/can_handle.lua b/drivers/SmartThings/zigbee-motion-sensor/src/zigbee-plugin-motion-sensor/can_handle.lua new file mode 100644 index 0000000000..9b926e0845 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/zigbee-plugin-motion-sensor/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_zigbee_plugin_motion_sensor = function(opts, driver, device) + local FINGERPRINTS = require("zigbee-plugin-motion-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return true, require("zigbee-plugin-motion-sensor") + end + end + return false +end + +return is_zigbee_plugin_motion_sensor diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/zigbee-plugin-motion-sensor/fingerprints.lua b/drivers/SmartThings/zigbee-motion-sensor/src/zigbee-plugin-motion-sensor/fingerprints.lua new file mode 100644 index 0000000000..459c3e7702 --- /dev/null +++ b/drivers/SmartThings/zigbee-motion-sensor/src/zigbee-plugin-motion-sensor/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_PLUGIN_MOTION_SENSOR_FINGERPRINTS = { + { model = "E280-KR0A0Z0-HA" } +} + +return ZIGBEE_PLUGIN_MOTION_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/zigbee-plugin-motion-sensor/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/zigbee-plugin-motion-sensor/init.lua index eb96b32a94..b387a35fbb 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/zigbee-plugin-motion-sensor/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/zigbee-plugin-motion-sensor/init.lua @@ -1,34 +1,13 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local zcl_clusters = require "st.zigbee.zcl.clusters" local device_management = require "st.zigbee.device_management" local OccupancySensing = zcl_clusters.OccupancySensing -local ZIGBEE_PLUGIN_MOTION_SENSOR_FINGERPRINTS = { - { model = "E280-KR0A0Z0-HA" } -} -local is_zigbee_plugin_motion_sensor = function(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_PLUGIN_MOTION_SENSOR_FINGERPRINTS) do - if device:get_model() == fingerprint.model then - return true - end - end - return false -end local function occupancy_attr_handler(driver, device, occupancy, zb_rx) device:emit_event(occupancy.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) @@ -59,7 +38,7 @@ local zigbee_plugin_motion_sensor = { [capabilities.refresh.commands.refresh.NAME] = do_refresh, } }, - can_handle = is_zigbee_plugin_motion_sensor + can_handle = require("zigbee-plugin-motion-sensor.can_handle"), } return zigbee_plugin_motion_sensor diff --git a/drivers/SmartThings/zigbee-power-meter/fingerprints.yml b/drivers/SmartThings/zigbee-power-meter/fingerprints.yml index 37cf9df678..ec58259dd5 100644 --- a/drivers/SmartThings/zigbee-power-meter/fingerprints.yml +++ b/drivers/SmartThings/zigbee-power-meter/fingerprints.yml @@ -28,6 +28,46 @@ zigbeeManufacturer: manufacturer: ShinaSystem model: "PMM-300Z3" deviceProfileName: power-meter-consumption-report-sihas + - id: "BITUO TECHNIK/SPM01-E0" + deviceLabel: Energy Monitor 1PN + manufacturer: BITUO TECHNIK + model: "SPM01-E0" + deviceProfileName: power-meter-1p + - id: "BITUO TECHNIK/SPM01X" + deviceLabel: Energy Monitor 1PN + manufacturer: BITUO TECHNIK + model: "SPM01X" + deviceProfileName: power-meter-1p + - id: "BITUO TECHNIK/SDM02-E0" + deviceLabel: Energy Monitor 2PN + manufacturer: BITUO TECHNIK + model: "SDM02-E0" + deviceProfileName: power-meter-2p + - id: "BITUO TECHNIK/SDM02X" + deviceLabel: Energy Monitor 2PN + manufacturer: BITUO TECHNIK + model: "SDM02X" + deviceProfileName: power-meter-2p + - id: "BITUO TECHNIK/SPM02-E0" + deviceLabel: Energy Monitor 3PN + manufacturer: BITUO TECHNIK + model: "SPM02-E0" + deviceProfileName: power-meter-3p + - id: "BITUO TECHNIK/SPM02X" + deviceLabel: Energy Monitor 3PN + manufacturer: BITUO TECHNIK + model: "SPM02X" + deviceProfileName: power-meter-3p + - id: "BITUO TECHNIK/SDM01W" + deviceLabel: Energy Monitor 3PN + manufacturer: BITUO TECHNIK + model: "SDM01W" + deviceProfileName: power-meter-3p + - id: "BITUO TECHNIK/SDM01B" + deviceLabel: Energy Monitor 1PN + manufacturer: BITUO TECHNIK + model: "SDM01B" + deviceProfileName: power-meter-1p zigbeeGeneric: - id: "genericMeter" deviceLabel: Zigbee Meter diff --git a/drivers/SmartThings/zigbee-power-meter/profiles/power-meter-1p.yml b/drivers/SmartThings/zigbee-power-meter/profiles/power-meter-1p.yml new file mode 100644 index 0000000000..7290794ffe --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/profiles/power-meter-1p.yml @@ -0,0 +1,31 @@ +name: power-meter-1p +components: +- id: main + label: Total Forward Energy + capabilities: + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: refresh + version: 1 + categories: + - name: CurbPowerMeter +- id: TotalReverseEnergy + label: Total Reverse Energy + capabilities: + - id: energyMeter + version: 1 + categories: + - name: CurbPowerMeter +- id: PhaseA + label: Phase A + capabilities: + - id: powerMeter + version: 1 + - id: currentMeasurement + version: 1 + - id: voltageMeasurement + version: 1 + categories: + - name: CurbPowerMeter \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-power-meter/profiles/power-meter-2p.yml b/drivers/SmartThings/zigbee-power-meter/profiles/power-meter-2p.yml new file mode 100644 index 0000000000..11f2dd412b --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/profiles/power-meter-2p.yml @@ -0,0 +1,42 @@ +name: power-meter-2p +components: +- id: main + label: Total Forward Energy + capabilities: + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: refresh + version: 1 + categories: + - name: CurbPowerMeter +- id: TotalReverseEnergy + label: Total Reverse Energy + capabilities: + - id: energyMeter + version: 1 + categories: + - name: CurbPowerMeter +- id: PhaseA + label: Phase A + capabilities: + - id: powerMeter + version: 1 + - id: currentMeasurement + version: 1 + - id: voltageMeasurement + version: 1 + categories: + - name: CurbPowerMeter +- id: PhaseB + label: Phase B + capabilities: + - id: powerMeter + version: 1 + - id: currentMeasurement + version: 1 + - id: voltageMeasurement + version: 1 + categories: + - name: CurbPowerMeter \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-power-meter/profiles/power-meter-3p.yml b/drivers/SmartThings/zigbee-power-meter/profiles/power-meter-3p.yml new file mode 100644 index 0000000000..20c824d092 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/profiles/power-meter-3p.yml @@ -0,0 +1,53 @@ +name: power-meter-3p +components: +- id: main + label: Total Forward Energy + capabilities: + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: refresh + version: 1 + categories: + - name: CurbPowerMeter +- id: TotalReverseEnergy + label: Total Reverse Energy + capabilities: + - id: energyMeter + version: 1 + categories: + - name: CurbPowerMeter +- id: PhaseA + label: Phase A + capabilities: + - id: powerMeter + version: 1 + - id: currentMeasurement + version: 1 + - id: voltageMeasurement + version: 1 + categories: + - name: CurbPowerMeter +- id: PhaseB + label: Phase B + capabilities: + - id: powerMeter + version: 1 + - id: currentMeasurement + version: 1 + - id: voltageMeasurement + version: 1 + categories: + - name: CurbPowerMeter +- id: PhaseC + label: Phase C + capabilities: + - id: powerMeter + version: 1 + - id: currentMeasurement + version: 1 + - id: voltageMeasurement + version: 1 + categories: + - name: CurbPowerMeter diff --git a/drivers/SmartThings/zigbee-power-meter/src/bituo/can_handle.lua b/drivers/SmartThings/zigbee-power-meter/src/bituo/can_handle.lua new file mode 100644 index 0000000000..a2124afdd6 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/bituo/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_bituo_power_meter = function(opts, driver, device) + local FINGERPRINTS = require("bituo.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return true, require("bituo") + end + end + + return false +end + +return is_bituo_power_meter diff --git a/drivers/SmartThings/zigbee-power-meter/src/bituo/fingerprints.lua b/drivers/SmartThings/zigbee-power-meter/src/bituo/fingerprints.lua new file mode 100644 index 0000000000..3909881854 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/bituo/fingerprints.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_POWER_METER_FINGERPRINTS = { + { mfr = "BITUO TECHNIK", model = "SPM01-E0" }, + { mfr = "BITUO TECHNIK", model = "SPM01X" }, + { mfr = "BITUO TECHNIK", model = "SDM02-E0" }, + { mfr = "BITUO TECHNIK", model = "SDM02X" }, + { mfr = "BITUO TECHNIK", model = "SPM02-E0" }, + { mfr = "BITUO TECHNIK", model = "SPM02X" }, + { mfr = "BITUO TECHNIK", model = "SDM01W" }, + { mfr = "BITUO TECHNIK", model = "SDM01B" } +} + +return ZIGBEE_POWER_METER_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-power-meter/src/bituo/init.lua b/drivers/SmartThings/zigbee-power-meter/src/bituo/init.lua new file mode 100644 index 0000000000..4bf5dbd359 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/bituo/init.lua @@ -0,0 +1,231 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local capabilities = require "st.capabilities" +local constants = require "st.zigbee.constants" +local clusters = require "st.zigbee.zcl.clusters" +local SimpleMetering = clusters.SimpleMetering +local ElectricalMeasurement = clusters.ElectricalMeasurement +local configurations = require "configurations" + + +local PHASE_A_CONFIGURATION = { + { + cluster = SimpleMetering.ID, + attribute = SimpleMetering.attributes.CurrentSummationDelivered.ID, + minimum_interval = 30, + maximum_interval = 120, + data_type = SimpleMetering.attributes.CurrentSummationDelivered.base_type, + reportable_change = 0 + }, + { + cluster = SimpleMetering.ID, + attribute = 0x0001, + minimum_interval = 30, + maximum_interval = 120, + data_type = SimpleMetering.attributes.CurrentSummationDelivered.base_type, + reportable_change = 0 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.ActivePower.ID, + minimum_interval = 30, + maximum_interval = 120, + data_type = ElectricalMeasurement.attributes.ActivePower.base_type, + reportable_change = 0 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSVoltage.ID, + minimum_interval = 30, + maximum_interval = 120, + data_type = ElectricalMeasurement.attributes.RMSVoltage.base_type, + reportable_change = 0 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSCurrent.ID, + minimum_interval = 30, + maximum_interval = 120, + data_type = ElectricalMeasurement.attributes.RMSCurrent.base_type, + reportable_change = 0 + } +} +local PHASE_B_CONFIGURATION = { + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.ActivePowerPhB.ID, + minimum_interval = 30, + maximum_interval = 120, + data_type = ElectricalMeasurement.attributes.ActivePowerPhB.base_type, + reportable_change = 0 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSVoltagePhB.ID, + minimum_interval = 30, + maximum_interval = 120, + data_type = ElectricalMeasurement.attributes.RMSVoltagePhB.base_type, + reportable_change = 0 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSCurrentPhB.ID, + minimum_interval = 30, + maximum_interval = 120, + data_type = ElectricalMeasurement.attributes.RMSCurrentPhB.base_type, + reportable_change = 0 + }, +} +local PHASE_C_CONFIGURATION = { + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.ActivePowerPhC.ID, + minimum_interval = 30, + maximum_interval = 120, + data_type = ElectricalMeasurement.attributes.ActivePowerPhC.base_type, + reportable_change = 0 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSVoltagePhC.ID, + minimum_interval = 30, + maximum_interval = 120, + data_type = ElectricalMeasurement.attributes.RMSVoltagePhC.base_type, + reportable_change = 0 + }, + { + cluster = ElectricalMeasurement.ID, + attribute = ElectricalMeasurement.attributes.RMSCurrentPhC.ID, + minimum_interval = 30, + maximum_interval = 120, + data_type = ElectricalMeasurement.attributes.RMSCurrentPhC.base_type, + reportable_change = 0 + } +} + +local function energy_handler(driver, device, value, zb_rx) + local multiplier = 1 + local divisor = 100 + local raw_value = value.value + local raw_value_kilowatts = raw_value * multiplier/divisor + + local offset = device:get_field(constants.ENERGY_METER_OFFSET) or 0 + if raw_value_kilowatts < offset then + --- somehow our value has gone below the offset, so we'll reset the offset, since the device seems to have + offset = 0 + device:set_field(constants.ENERGY_METER_OFFSET, offset, {persist = true}) + end + raw_value_kilowatts = raw_value_kilowatts - offset + + local raw_value_watts = raw_value_kilowatts*1000 + local delta_tick + local last_save_ticks = device:get_field("LAST_SAVE_TICK") + + if last_save_ticks == nil then last_save_ticks = 0 end + delta_tick = os.time() - last_save_ticks + + -- wwst energy certification : powerConsumptionReport capability values should be updated every 15 minutes. + -- Check if 15 minutes have passed since the reporting time of current_power_consumption. + if delta_tick >= 15*60 then + local delta_energy = 0.0 + local current_power_consumption = device:get_latest_state("main", capabilities.powerConsumptionReport.ID, capabilities.powerConsumptionReport.powerConsumption.NAME) + if current_power_consumption ~= nil then + delta_energy = math.max(raw_value_watts - current_power_consumption.energy, 0.0) + end + device:emit_event(capabilities.powerConsumptionReport.powerConsumption({energy = raw_value_watts, deltaEnergy = delta_energy })) -- the unit of these values should be 'Wh' + + local curr_save_tick = last_save_ticks + 15*60 -- Set the time to a regular interval by adding 15 minutes to the existing last_save_ticks. + -- If the time 15 minutes from now is less than the current time, set the current time as the last time. + if curr_save_tick + 15*60 < os.time() then + curr_save_tick = os.time() + end + device:set_field("LAST_SAVE_TICK", curr_save_tick, {persist = false}) + end + device:emit_event(capabilities.energyMeter.energy({value = raw_value_kilowatts, unit = "kWh"})) +end + +local function generic_handler_factory(component_name, capability, multiplier, divisor, unit) + return function(driver, device, value, zb_rx) + local component = device.profile.components[component_name] + if component ~= nil then + local raw_value = value.value * multiplier / divisor + device:emit_component_event(component, capability({value = raw_value, unit = unit})) + end + end +end + +local refresh = function(driver, device, cmd) + device:refresh() +end + +local function resetEnergyMeter(self, device) + device:send(clusters.OnOff.server.commands.On(device)) + -- Reset Power consumption + device:set_field(constants.ENERGY_METER_OFFSET, 0, {persist = true}) + device:set_field("LAST_SAVE_TICK", os.time(), {persist = false}) +end +local function do_configure(driver, device) + device:configure() + --device:send(device_management.build_bind_request(device, clusters.SimpleMetering.ID, driver.environment_info.hub_zigbee_eui)) + --device:send(device_management.build_bind_request(device, clusters.ElectricalMeasurement.ID, driver.environment_info.hub_zigbee_eui)) + device:refresh() +end + +local device_init = function(self, device) + for _, attribute in ipairs(PHASE_A_CONFIGURATION) do + device:add_configured_attribute(attribute) + device:add_monitored_attribute(attribute) + end + if string.find(device:get_model(), "SDM02") or string.find(device:get_model(), "SPM02") or string.find(device:get_model(), "SDM01W") then + for _, attribute in ipairs(PHASE_B_CONFIGURATION) do + device:add_configured_attribute(attribute) + device:add_monitored_attribute(attribute) + end + end + if string.find(device:get_model(), "SPM02") or string.find(device:get_model(), "SDM01W") then + for _, attribute in ipairs(PHASE_C_CONFIGURATION) do + device:add_configured_attribute(attribute) + device:add_monitored_attribute(attribute) + end + end +end + +local bituo_power_meter_handler = { + NAME = "bituo power meter handler", + lifecycle_handlers = { + init = configurations.power_reconfig_wrapper(device_init), + doConfigure = do_configure, + }, + zigbee_handlers = { + attr = { + [clusters.SimpleMetering.ID] = { + [clusters.SimpleMetering.attributes.CurrentSummationDelivered.ID] = energy_handler, + [0x0001] = generic_handler_factory("TotalReverseEnergy", capabilities.energyMeter.energy, 1, 100, "kWh"), + }, + [clusters.ElectricalMeasurement.ID] = { + [ElectricalMeasurement.attributes.ActivePower.ID] = generic_handler_factory("PhaseA", capabilities.powerMeter.power, 1, 1, "W"), + [ElectricalMeasurement.attributes.ActivePowerPhB.ID] = generic_handler_factory("PhaseB", capabilities.powerMeter.power, 1, 1, "W"), + [ElectricalMeasurement.attributes.ActivePowerPhC.ID] = generic_handler_factory("PhaseC", capabilities.powerMeter.power, 1, 1, "W"), + [ElectricalMeasurement.attributes.RMSVoltage.ID] = generic_handler_factory("PhaseA", capabilities.voltageMeasurement.voltage, 1, 100, "V"), + [ElectricalMeasurement.attributes.RMSVoltagePhB.ID] = generic_handler_factory("PhaseB", capabilities.voltageMeasurement.voltage, 1, 100, "V"), + [ElectricalMeasurement.attributes.RMSVoltagePhC.ID] = generic_handler_factory("PhaseC", capabilities.voltageMeasurement.voltage, 1, 100, "V"), + [ElectricalMeasurement.attributes.RMSCurrent.ID] = generic_handler_factory("PhaseA", capabilities.currentMeasurement.current, 1, 100, "A"), + [ElectricalMeasurement.attributes.RMSCurrentPhB.ID] = generic_handler_factory("PhaseB", capabilities.currentMeasurement.current, 1, 100, "A"), + [ElectricalMeasurement.attributes.RMSCurrentPhC.ID] = generic_handler_factory("PhaseC", capabilities.currentMeasurement.current, 1, 100, "A") + } + } + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = refresh + }, + [capabilities.energyMeter.ID] = { + [capabilities.energyMeter.commands.resetEnergyMeter.NAME] = resetEnergyMeter, + }, + }, + can_handle = require("bituo.can_handle"), +} + +return bituo_power_meter_handler diff --git a/drivers/SmartThings/zigbee-power-meter/src/ezex/can_handle.lua b/drivers/SmartThings/zigbee-power-meter/src/ezex/can_handle.lua new file mode 100644 index 0000000000..91569fd4b4 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/ezex/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_ezex_power_meter = function(opts, driver, device) + local FINGERPRINTS = require("ezex.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return true, require("ezex") + end + end + + return false +end + +return is_ezex_power_meter diff --git a/drivers/SmartThings/zigbee-power-meter/src/ezex/fingerprints.lua b/drivers/SmartThings/zigbee-power-meter/src/ezex/fingerprints.lua new file mode 100644 index 0000000000..1495f82c56 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/ezex/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_POWER_METER_FINGERPRINTS = { + { model = "E240-KR080Z0-HA" } +} + +return ZIGBEE_POWER_METER_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-power-meter/src/ezex/init.lua b/drivers/SmartThings/zigbee-power-meter/src/ezex/init.lua index 9e4c6d6eea..15ca345f31 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/ezex/init.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/ezex/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local constants = require "st.zigbee.constants" @@ -20,19 +10,7 @@ local ElectricalMeasurement = clusters.ElectricalMeasurement local energy_meter_defaults = require "st.zigbee.defaults.energyMeter_defaults" local configurations = require "configurations" -local ZIGBEE_POWER_METER_FINGERPRINTS = { - { model = "E240-KR080Z0-HA" } -} -local is_ezex_power_meter = function(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_POWER_METER_FINGERPRINTS) do - if device:get_model() == fingerprint.model then - return true - end - end - - return false -end local instantaneous_demand_configuration = { cluster = SimpleMetering.ID, @@ -88,7 +66,7 @@ local ezex_power_meter_handler = { init = configurations.power_reconfig_wrapper(device_init), doConfigure = do_configure, }, - can_handle = is_ezex_power_meter + can_handle = require("ezex.can_handle"), } return ezex_power_meter_handler diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/can_handle.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/can_handle.lua new file mode 100644 index 0000000000..3cf7077ddd --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_frient_power_meter = function(opts, driver, device) + local FINGERPRINTS = require("frient.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return true, require("frient") + end + end + + return false +end + +return is_frient_power_meter diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/fingerprints.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/fingerprints.lua new file mode 100644 index 0000000000..5bc09f600d --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_POWER_METER_FINGERPRINTS = { + { model = "ZHEMI101" }, + { model = "EMIZB-132" }, +} + +return ZIGBEE_POWER_METER_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-power-meter/src/frient/init.lua b/drivers/SmartThings/zigbee-power-meter/src/frient/init.lua index 9d3f6f83b0..5933faf5cb 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/frient/init.lua @@ -1,34 +1,11 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local constants = require "st.zigbee.constants" local configurations = require "configurations" -local ZIGBEE_POWER_METER_FINGERPRINTS = { - { model = "ZHEMI101" }, - { model = "EMIZB-132" }, -} - -local is_frient_power_meter = function(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_POWER_METER_FINGERPRINTS) do - if device:get_model() == fingerprint.model then - return true - end - end - return false -end local do_configure = function(self, device) device:refresh() @@ -46,7 +23,7 @@ local frient_power_meter_handler = { init = configurations.power_reconfig_wrapper(device_init), doConfigure = do_configure, }, - can_handle = is_frient_power_meter + can_handle = require("frient.can_handle"), } return frient_power_meter_handler \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-power-meter/src/init.lua b/drivers/SmartThings/zigbee-power-meter/src/init.lua index ae98baca8b..f15fae7905 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/init.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local ZigbeeDriver = require "st.zigbee" @@ -63,11 +53,7 @@ local zigbee_power_meter_driver_template = { } }, current_config_version = 1, - sub_drivers = { - require("ezex"), - require("frient"), - require("shinasystems"), - }, + sub_drivers = require("sub_drivers"), lifecycle_handlers = { init = configurations.power_reconfig_wrapper(device_init), doConfigure = do_configure, diff --git a/drivers/SmartThings/zigbee-power-meter/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-power-meter/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/lazy_load_subdriver.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(sub_driver_name) + -- gets the current lua libs api version + local version = require "version" + local ZigbeeDriver = require "st.zigbee" + if version.api >= 16 then + return ZigbeeDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZigbeeDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end +end diff --git a/drivers/SmartThings/zigbee-power-meter/src/shinasystems/can_handle.lua b/drivers/SmartThings/zigbee-power-meter/src/shinasystems/can_handle.lua new file mode 100644 index 0000000000..94808a8486 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/shinasystems/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_shinasystems_power_meter = function(opts, driver, device) + local FINGERPRINTS = require("shinasystems.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return true, require("shinasystems") + end + end + + return false +end + +return is_shinasystems_power_meter diff --git a/drivers/SmartThings/zigbee-power-meter/src/shinasystems/fingerprints.lua b/drivers/SmartThings/zigbee-power-meter/src/shinasystems/fingerprints.lua new file mode 100644 index 0000000000..5364835514 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/shinasystems/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_POWER_METER_FINGERPRINTS = { + { model = "PMM-300Z1" }, + { model = "PMM-300Z2" }, + { model = "PMM-300Z3" } +} + +return ZIGBEE_POWER_METER_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-power-meter/src/shinasystems/init.lua b/drivers/SmartThings/zigbee-power-meter/src/shinasystems/init.lua index 64713547a8..54866e2f82 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/shinasystems/init.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/shinasystems/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local constants = require "st.zigbee.constants" @@ -19,11 +9,6 @@ local SimpleMetering = clusters.SimpleMetering local ElectricalMeasurement = clusters.ElectricalMeasurement local configurations = require "configurations" -local ZIGBEE_POWER_METER_FINGERPRINTS = { - { model = "PMM-300Z1" }, - { model = "PMM-300Z2" }, - { model = "PMM-300Z3" } -} local POWERMETER_CONFIGURATION_V2 = { { @@ -52,15 +37,6 @@ local POWERMETER_CONFIGURATION_V2 = { } } -local is_shinasystems_power_meter = function(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_POWER_METER_FINGERPRINTS) do - if device:get_model() == fingerprint.model then - return true - end - end - - return false -end local function energy_meter_handler(driver, device, value, zb_rx) local multiplier = device:get_field(constants.SIMPLE_METERING_MULTIPLIER_KEY) or 1 @@ -128,7 +104,7 @@ local shinasystems_power_meter_handler = { init = configurations.power_reconfig_wrapper(device_init), doConfigure = do_configure, }, - can_handle = is_shinasystems_power_meter + can_handle = require("shinasystems.can_handle"), } return shinasystems_power_meter_handler diff --git a/drivers/SmartThings/zigbee-power-meter/src/sub_drivers.lua b/drivers/SmartThings/zigbee-power-meter/src/sub_drivers.lua new file mode 100644 index 0000000000..51b24aca32 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/sub_drivers.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" +local sub_drivers = { + lazy_load_if_possible("ezex"), + lazy_load_if_possible("frient"), + lazy_load_if_possible("shinasystems"), + lazy_load_if_possible("bituo"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p.lua new file mode 100644 index 0000000000..5308e11ee2 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p.lua @@ -0,0 +1,225 @@ +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local ElectricalMeasurement = clusters.ElectricalMeasurement +local SimpleMetering = clusters.SimpleMetering +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" + + +-- Mock out globals +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("power-meter-1p.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "BITUO TECHNIK", + model = "SPM01X", + server_clusters = {SimpleMetering.ID, ElectricalMeasurement.ID} + } + } +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_device) + zigbee_test_utils.init_noop_health_check_timer() +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "SimpleMetering event should be handled by powerConsumptionReport capability", + function() + test.timer.__create_and_queue_test_time_advance_timer(15*60, "oneshot") + -- #1 : 15 minutes have passed + test.mock_time.advance_time(15*60) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device,150) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({energy = 1500.0, deltaEnergy = 0.0 })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 1.5, unit = "kWh"})) + ) + -- #2 : Not even 15 minutes passed + test.wait_for_events() + test.mock_time.advance_time(1*60) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device,170) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 1.7, unit = "kWh"})) + ) + -- #3 : 15 minutes have passed + test.wait_for_events() + test.mock_time.advance_time(14*60) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device,200) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({energy = 2000.0, deltaEnergy = 500.0 })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 2.0, unit = "kWh"})) + ) + end +) + +test.register_message_test( + "ActivePower Report should be handled. Sensor value is in W, capability attribute value is in hectowatts", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.ActivePower:build_test_attr_report(mock_device, + 27) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseA", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) + } + } +) + +test.register_message_test( + "ActivePower Report should be handled. Sensor value is in W, capability attribute value is in hectowatts", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.ActivePower:build_test_attr_report(mock_device, + 27) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseA", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) + } + } +) + +test.register_message_test( + "RMSCurrent Report for PhaseA should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.RMSCurrent:build_test_attr_report(mock_device, + 34) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseA", capabilities.currentMeasurement.current({ value = 0.34, unit = "A" })) + } + } +) + +test.register_message_test( + "RMSVoltage Report for PhaseA should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.RMSVoltage:build_test_attr_report(mock_device, + 22000) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseA", capabilities.voltageMeasurement.voltage({ value = 220.0, unit = "V" })) + } + } +) + +test.register_coroutine_test( + "Device configure lifecycle event should configure device properly", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, SimpleMetering.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ElectricalMeasurement.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePower:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltage:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrent:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.configure_reporting(mock_device, data_types.ClusterId(SimpleMetering.ID), data_types.AttributeId(0x0001), data_types.ZigbeeDataType(SimpleMetering.attributes.CurrentSummationDelivered.base_type.ID), 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerDivisor:configure_reporting(mock_device, 1, 43200, 1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.InstantaneousDemand:configure_reporting(mock_device, 5, 3600, 5) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerMultiplier:configure_reporting(mock_device, 1, 43200, 1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.InstantaneousDemand:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.read_attribute(mock_device, data_types.ClusterId(SimpleMetering.ID), data_types.AttributeId(0x0001)) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePower:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltage:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrent:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) + }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_2p.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_2p.lua new file mode 100644 index 0000000000..b85c955005 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_2p.lua @@ -0,0 +1,281 @@ +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local ElectricalMeasurement = clusters.ElectricalMeasurement +local SimpleMetering = clusters.SimpleMetering +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" + +-- 使用两相电能表配置文件 +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("power-meter-2p.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "BITUO TECHNIK", + model = "SDM02X", + server_clusters = {SimpleMetering.ID, ElectricalMeasurement.ID} + } + } +}) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.mock_device.add_test_device(mock_device) + zigbee_test_utils.init_noop_health_check_timer() +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "SimpleMetering event should be handled by powerConsumptionReport capability", + function() + test.timer.__create_and_queue_test_time_advance_timer(15*60, "oneshot") + -- #1 : 15 minutes have passed + test.mock_time.advance_time(15*60) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device,150) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({energy = 1500.0, deltaEnergy = 0.0 })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 1.5, unit = "kWh"})) + ) + -- #2 : Not even 15 minutes passed + test.wait_for_events() + test.mock_time.advance_time(1*60) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device,170) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 1.7, unit = "kWh"})) + ) + -- #3 : 15 minutes have passed + test.wait_for_events() + test.mock_time.advance_time(14*60) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device,200) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({energy = 2000.0, deltaEnergy = 500.0 })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 2.0, unit = "kWh"})) + ) + end +) + +test.register_message_test( + "ActivePower Report should be handled. Sensor value is in W, capability attribute value is in hectowatts", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.ActivePower:build_test_attr_report(mock_device, + 27) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseA", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) + } + } +) + +test.register_message_test( + "RMSCurrent Report for PhaseA should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.RMSCurrent:build_test_attr_report(mock_device, + 34) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseA", capabilities.currentMeasurement.current({ value = 0.34, unit = "A" })) + } + } +) + +test.register_message_test( + "RMSVoltage Report for PhaseB should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.RMSVoltage:build_test_attr_report(mock_device, + 22000) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseA", capabilities.voltageMeasurement.voltage({ value = 220.0, unit = "V" })) + } + } +) + +test.register_message_test( + "ActivePower Report should be handled. Sensor value is in W, capability attribute value is in hectowatts", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.ActivePowerPhB:build_test_attr_report(mock_device, + 27) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseB", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) + } + } +) + +test.register_message_test( + "RMSCurrent Report for PhaseB should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.RMSCurrentPhB:build_test_attr_report(mock_device, + 34) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseB", capabilities.currentMeasurement.current({ value = 0.34, unit = "A" })) + } + } +) + +test.register_message_test( + "RMSVoltage Report for PhaseB should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.RMSVoltagePhB:build_test_attr_report(mock_device, + 22000) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseB", capabilities.voltageMeasurement.voltage({ value = 220.0, unit = "V" })) + } + } +) + +test.register_coroutine_test( + "Device configure lifecycle event should configure device properly", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, SimpleMetering.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ElectricalMeasurement.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePower:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltage:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrent:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePowerPhB:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltagePhB:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrentPhB:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.configure_reporting(mock_device, data_types.ClusterId(SimpleMetering.ID), data_types.AttributeId(0x0001), data_types.ZigbeeDataType(SimpleMetering.attributes.CurrentSummationDelivered.base_type.ID), 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.read_attribute(mock_device, data_types.ClusterId(SimpleMetering.ID), data_types.AttributeId(0x0001)) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePower:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltage:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrent:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePowerPhB:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltagePhB:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrentPhB:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.InstantaneousDemand:configure_reporting(mock_device, 5, 3600, 5) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerDivisor:configure_reporting(mock_device, 1, 43200, 1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerMultiplier:configure_reporting(mock_device, 1, 43200, 1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.InstantaneousDemand:read(mock_device) + }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_3p.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_3p.lua new file mode 100644 index 0000000000..fad26aad40 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_3p.lua @@ -0,0 +1,355 @@ +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local ElectricalMeasurement = clusters.ElectricalMeasurement +local SimpleMetering = clusters.SimpleMetering +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" + +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("power-meter-3p.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "BITUO TECHNIK", + model = "SDM01W", + server_clusters = {SimpleMetering.ID, ElectricalMeasurement.ID} + } + } +}) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.mock_device.add_test_device(mock_device) + zigbee_test_utils.init_noop_health_check_timer() +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "SimpleMetering event should be handled by powerConsumptionReport capability", + function() + test.timer.__create_and_queue_test_time_advance_timer(15*60, "oneshot") + -- #1 : 15 minutes have passed + test.mock_time.advance_time(15*60) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device,150) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({energy = 1500.0, deltaEnergy = 0.0 })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 1.5, unit = "kWh"})) + ) + -- #2 : Not even 15 minutes passed + test.wait_for_events() + test.mock_time.advance_time(1*60) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device,170) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 1.7, unit = "kWh"})) + ) + -- #3 : 15 minutes have passed + test.wait_for_events() + test.mock_time.advance_time(14*60) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device,200) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({energy = 2000.0, deltaEnergy = 500.0 })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 2.0, unit = "kWh"})) + ) + end +) + +test.register_message_test( + "ActivePower Report should be handled. Sensor value is in W, capability attribute value is in hectowatts", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.ActivePower:build_test_attr_report(mock_device, + 27) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseA", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) + } + } +) + +test.register_message_test( + "RMSCurrent Report for PhaseA should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.RMSCurrent:build_test_attr_report(mock_device, + 34) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseA", capabilities.currentMeasurement.current({ value = 0.34, unit = "A" })) + } + } +) + +test.register_message_test( + "RMSVoltage Report for PhaseA should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.RMSVoltage:build_test_attr_report(mock_device, + 22000) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseA", capabilities.voltageMeasurement.voltage({ value = 220.0, unit = "V" })) + } + } +) + +test.register_message_test( + "ActivePower Report for PhaseB should be handled. Sensor value is in W, capability attribute value is in hectowatts", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.ActivePowerPhB:build_test_attr_report(mock_device, + 27) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseB", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) + } + } +) + +test.register_message_test( + "RMSCurrent Report for PhaseB should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.RMSCurrentPhB:build_test_attr_report(mock_device, + 34) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseB", capabilities.currentMeasurement.current({ value = 0.34, unit = "A" })) + } + } +) + +test.register_message_test( + "RMSVoltage Report for PhaseB should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.RMSVoltagePhB:build_test_attr_report(mock_device, + 22000) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseB", capabilities.voltageMeasurement.voltage({ value = 220.0, unit = "V" })) + } + } +) + +test.register_message_test( + "ActivePower Report for PhaseC should be handled. Sensor value is in W, capability attribute value is in hectowatts", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.ActivePowerPhC:build_test_attr_report(mock_device, + 27) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseC", capabilities.powerMeter.power({ value = 27.0, unit = "W" })) + } + } +) + +test.register_message_test( + "RMSCurrent Report for PhaseC should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.RMSCurrentPhC:build_test_attr_report(mock_device, + 34) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseC", capabilities.currentMeasurement.current({ value = 0.34, unit = "A" })) + } + } +) + +test.register_message_test( + "RMSVoltage Report for PhaseC should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, ElectricalMeasurement.attributes.RMSVoltagePhC:build_test_attr_report(mock_device, + 22000) }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("PhaseC", capabilities.voltageMeasurement.voltage({ value = 220.0, unit = "V" })) + } + } +) + +test.register_coroutine_test( + "Device configure lifecycle event should configure device properly", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, SimpleMetering.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ElectricalMeasurement.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePower:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltage:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrent:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePowerPhB:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltagePhB:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrentPhB:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePowerPhC:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltagePhC:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrentPhC:configure_reporting(mock_device, 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.configure_reporting(mock_device, data_types.ClusterId(SimpleMetering.ID), data_types.AttributeId(0x0001), data_types.ZigbeeDataType(SimpleMetering.attributes.CurrentSummationDelivered.base_type.ID), 30, 120, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.read_attribute(mock_device, data_types.ClusterId(SimpleMetering.ID), data_types.AttributeId(0x0001)) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePower:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltage:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrent:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePowerPhB:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltagePhB:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrentPhB:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePowerPhC:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltagePhC:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrentPhC:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.InstantaneousDemand:configure_reporting(mock_device, 5, 3600, 5) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerDivisor:configure_reporting(mock_device, 1, 43200, 1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerMultiplier:configure_reporting(mock_device, 1, 43200, 1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.InstantaneousDemand:read(mock_device) + }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/aqara/can_handle.lua b/drivers/SmartThings/zigbee-presence-sensor/src/aqara/can_handle.lua new file mode 100644 index 0000000000..33a0bde838 --- /dev/null +++ b/drivers/SmartThings/zigbee-presence-sensor/src/aqara/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_aqara_products = function(opts, driver, device, ...) + local FINGERPRINTS = { mfr = "aqara", model = "lumi.motion.ac01" } + + if device:get_manufacturer() == FINGERPRINTS.mfr and device:get_model() == FINGERPRINTS.model then + return true, require("aqara") + end + return false +end + +return is_aqara_products diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/aqara/init.lua b/drivers/SmartThings/zigbee-presence-sensor/src/aqara/init.lua index b9a0bfb001..1fd79e3875 100644 --- a/drivers/SmartThings/zigbee-presence-sensor/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-presence-sensor/src/aqara/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" @@ -19,12 +22,6 @@ local SENSITIVITY = "stse.sensitivity" local RESET_PRESENCE = "stse.resetPresence" local APP_DISTANCE = "stse.approachDistance" -local FINGERPRINTS = { mfr = "aqara", model = "lumi.motion.ac01" } - -local is_aqara_products = function(opts, driver, device, ...) - return device:get_manufacturer() == FINGERPRINTS.mfr and device:get_model() == FINGERPRINTS.model -end - local function device_init(driver, device) -- no action end @@ -104,7 +101,7 @@ local aqara_fp1_handler = { doConfigure = do_configure, infoChanged = device_info_changed }, - can_handle = is_aqara_products + can_handle = require("aqara.can_handle"), } return aqara_fp1_handler diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/arrival-sensor-v1/can_handle.lua b/drivers/SmartThings/zigbee-presence-sensor/src/arrival-sensor-v1/can_handle.lua new file mode 100644 index 0000000000..d848d46caa --- /dev/null +++ b/drivers/SmartThings/zigbee-presence-sensor/src/arrival-sensor-v1/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function arrival_sensor_v1_can_handle(opts, driver, device, ...) + -- excluding Aqara device and tagv4 + if device:get_manufacturer() ~= "aqara" and device:get_model() ~= "tagv4" then + return true, require("arrival-sensor-v1") + end + return false +end + +return arrival_sensor_v1_can_handle diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/arrival-sensor-v1/init.lua b/drivers/SmartThings/zigbee-presence-sensor/src/arrival-sensor-v1/init.lua index b7bece2efe..81d9461a38 100644 --- a/drivers/SmartThings/zigbee-presence-sensor/src/arrival-sensor-v1/init.lua +++ b/drivers/SmartThings/zigbee-presence-sensor/src/arrival-sensor-v1/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Zigbee Spec Utils local zcl_messages = require "st.zigbee.zcl" @@ -41,10 +31,6 @@ local presence_utils = require "presence_utils" local CHECKIN_INTERVAL = 20 -- seconds -local function arrival_sensor_v1_can_handle(opts, driver, device, ...) - -- excluding Aqara device and tagv4 - return device:get_manufacturer() ~= "aqara" and device:get_model() ~= "tagv4" -end local function legacy_battery_handler(self, device, zb_rx) local battery_value = string.byte(zb_rx.body.zcl_body.body_bytes) @@ -144,7 +130,7 @@ local arrival_sensor_v1 = { added = added_handler, init = init_handler }, - can_handle = arrival_sensor_v1_can_handle + can_handle = require("arrival-sensor-v1.can_handle"), } return arrival_sensor_v1 diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/init.lua b/drivers/SmartThings/zigbee-presence-sensor/src/init.lua index cc1611f2ec..261bc90922 100644 --- a/drivers/SmartThings/zigbee-presence-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-presence-sensor/src/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local ZigbeeDriver = require "st.zigbee" local defaults = require "st.zigbee.defaults" @@ -204,10 +194,7 @@ local zigbee_presence_driver = { }, -- Custom handler for every Zigbee message zigbee_message_handler = all_zigbee_message_handler, - sub_drivers = { - require("aqara"), - require("arrival-sensor-v1") - }, + sub_drivers = require("sub_drivers"), health_check = false, } diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-presence-sensor/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-presence-sensor/src/lazy_load_subdriver.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(sub_driver_name) + -- gets the current lua libs api version + local version = require "version" + local ZigbeeDriver = require "st.zigbee" + if version.api >= 16 then + return ZigbeeDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZigbeeDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end +end diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/presence_utils.lua b/drivers/SmartThings/zigbee-presence-sensor/src/presence_utils.lua index a68de0f95e..968d75ea54 100644 --- a/drivers/SmartThings/zigbee-presence-sensor/src/presence_utils.lua +++ b/drivers/SmartThings/zigbee-presence-sensor/src/presence_utils.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local presence_utils = {} diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/sub_drivers.lua b/drivers/SmartThings/zigbee-presence-sensor/src/sub_drivers.lua new file mode 100644 index 0000000000..1340826663 --- /dev/null +++ b/drivers/SmartThings/zigbee-presence-sensor/src/sub_drivers.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" +local sub_drivers = { + lazy_load_if_possible("aqara"), + lazy_load_if_possible("arrival-sensor-v1"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/test/test_st_arrival_sensor_v1.lua b/drivers/SmartThings/zigbee-presence-sensor/src/test/test_st_arrival_sensor_v1.lua index 4c2533036d..123edc9de1 100644 --- a/drivers/SmartThings/zigbee-presence-sensor/src/test/test_st_arrival_sensor_v1.lua +++ b/drivers/SmartThings/zigbee-presence-sensor/src/test/test_st_arrival_sensor_v1.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/test/test_zigbee_presence_sensor.lua b/drivers/SmartThings/zigbee-presence-sensor/src/test/test_zigbee_presence_sensor.lua index 55a0d7cf51..6957148647 100644 --- a/drivers/SmartThings/zigbee-presence-sensor/src/test/test_zigbee_presence_sensor.lua +++ b/drivers/SmartThings/zigbee-presence-sensor/src/test/test_zigbee_presence_sensor.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml index 76b105fdb8..ddd1135cbd 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml +++ b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml @@ -34,6 +34,11 @@ zigbeeManufacturer: manufacturer: frient A/S model: SMSZB-120 deviceProfileName: smoke-temp-battery-alarm + - id: "frient/HESZB-120" + deviceLabel: frient Heat Detector + manufacturer: frient A/S + model: HESZB-120 + deviceProfileName: heat-temp-battery-alarm - id: "Heiman/Orvibo/Gas3" deviceLabel: Orvibo Gas Detector manufacturer: Heiman diff --git a/drivers/SmartThings/zigbee-smoke-detector/profiles/heat-temp-battery-alarm.yml b/drivers/SmartThings/zigbee-smoke-detector/profiles/heat-temp-battery-alarm.yml new file mode 100644 index 0000000000..c7abdd4e99 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/profiles/heat-temp-battery-alarm.yml @@ -0,0 +1,55 @@ +name: heat-temp-battery-alarm +components: +- id: main + capabilities: + - id: temperatureAlarm + version: 1 + config: + values: + - key: "temperatureAlarm.value" + enabledValues: + - heat + - cleared + - id: temperatureMeasurement + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + - id: alarm + version: 1 + config: + values: + - key: "alarm.value" + enabledValues: + - off + - siren + - key: "{{enumCommands}}" + enabledValues: + - off + - siren + categories: + - name: TempSensor +preferences: + - preferenceId: tempOffset + explicit: true + - name: "tempSensitivity" + title: "Temperature Sensitivity (°C)" + description: "Minimum change in temperature to report" + required: false + preferenceType: number + definition: + minimum: 0.1 + maximum: 2.0 + default: 1.0 + - name: "warningDuration" + title: "Alarm duration (s)" + description: "After how many seconds should the alarm turn off" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 65534 + default: 240 \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/frient/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/frient/init.lua index b882c3012a..1d6d85311a 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/frient/init.lua @@ -21,6 +21,7 @@ local cluster_base = require "st.zigbee.cluster_base" local Basic = zcl_clusters.Basic local alarm = capabilities.alarm local smokeDetector = capabilities.smokeDetector +local temperatureAlarm = capabilities.temperatureAlarm local IASWD = zcl_clusters.IASWD local IASZone = zcl_clusters.IASZone @@ -72,7 +73,14 @@ end local function device_added(driver, device) device:emit_event(alarm.alarm.off()) - device:emit_event(smokeDetector.smoke.clear()) + + if device:supports_capability(capabilities.temperatureAlarm) then + device:emit_event(temperatureAlarm.temperatureAlarm.cleared()) + end + + if device:supports_capability(capabilities.smokeDetector) then + device:emit_event(smokeDetector.smoke.clear()) + end device:send(cluster_base.read_manufacturer_specific_attribute(device, Basic.ID, DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR, DEVELCO_MANUFACTURER_CODE)) end @@ -111,14 +119,28 @@ local info_changed = function (driver, device, event, args) end local function generate_event_from_zone_status(driver, device, zone_status, zigbee_message) - if zone_status:is_test_set() then - device:emit_event(smokeDetector.smoke.tested()) + if device:supports_capability(capabilities.temperatureAlarm) then + device:emit_event(temperatureAlarm.temperatureAlarm.heat()) + end + if device:supports_capability(capabilities.smokeDetector) then + device:emit_event(smokeDetector.smoke.tested()) + end elseif zone_status:is_alarm1_set() then - device:emit_event(smokeDetector.smoke.detected()) + if device:supports_capability(capabilities.temperatureAlarm) then + device:emit_event(temperatureAlarm.temperatureAlarm.heat()) + end + if device:supports_capability(capabilities.smokeDetector) then + device:emit_event(smokeDetector.smoke.detected()) + end else device.thread:call_with_delay(6, function () - device:emit_event(smokeDetector.smoke.clear()) + if device:supports_capability(capabilities.temperatureAlarm) then + device:emit_event(temperatureAlarm.temperatureAlarm.cleared()) + end + if device:supports_capability(capabilities.smokeDetector) then + device:emit_event(smokeDetector.smoke.clear()) + end end) end end @@ -249,7 +271,7 @@ local frient_smoke_sensor = { } }, can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "frient A/S" and device:get_model() == "SMSZB-120" + return device:get_manufacturer() == "frient A/S" and (device:get_model() == "SMSZB-120" or device:get_model() == "HESZB-120") end } return frient_smoke_sensor diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/init.lua index 2508061813..941c3312da 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/init.lua @@ -22,7 +22,8 @@ local zigbee_smoke_driver_template = { capabilities.smokeDetector, capabilities.battery, capabilities.alarm, - capabilities.temperatureMeasurement + capabilities.temperatureMeasurement, + capabilities.temperatureAlarm }, sub_drivers = { require("frient"), diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_heat_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_heat_detector.lua new file mode 100644 index 0000000000..aec09a8435 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_heat_detector.lua @@ -0,0 +1,583 @@ +-- Copyright 2025 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. + +-- Mock out globals +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local IASWD = clusters.IASWD +local IASZone = clusters.IASZone +local PowerConfiguration = clusters.PowerConfiguration +local TemperatureMeasurement = clusters.TemperatureMeasurement +local Basic = clusters.Basic +local capabilities = require "st.capabilities" +local alarm = capabilities.alarm +local temperatureAlarm = capabilities.temperatureAlarm +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local IasEnrollResponseCode = require "st.zigbee.generated.zcl_clusters.IASZone.types.EnrollResponseCode" +local t_utils = require "integration_test.utils" +local data_types = require "st.zigbee.data_types" +local SirenConfiguration = require "st.zigbee.generated.zcl_clusters.IASWD.types.SirenConfiguration" +local ALARM_DEFAULT_MAX_DURATION = 0x00F0 +local POWER_CONFIGURATION_ENDPOINT = 0x23 +local IASZONE_ENDPOINT = 0x23 +local TEMPERATURE_MEASUREMENT_ENDPOINT = 0x26 +local base64 = require "base64" +local PRIMARY_SW_VERSION = "primary_sw_version" +local SIREN_ENDIAN = "siren_endian" +local DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR = 0x8000 +local DEVELCO_MANUFACTURER_CODE = 0x1015 +local cluster_base = require "st.zigbee.cluster_base" +local defaultWarningDuration = 240 + +local mock_device = test.mock_device.build_test_zigbee_device( + { profile = t_utils.get_profile_definition("heat-temp-battery-alarm.yml"), + zigbee_endpoints = { + [0x01] = { + id = 0x01, + manufacturer = "frient A/S", + model = "HESZB-120", + server_clusters = { 0x0003, 0x0005, 0x0006 } + }, + [0x23] = { + id = 0x23, + server_clusters = { 0x0000, 0x0001, 0x0003, 0x000f, 0x0020, 0x0500, 0x0502 } + }, + [0x26] = { + id = 0x26, + server_clusters = { 0x0000, 0x0003, 0x0402 } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.mock_device.add_test_device(mock_device)end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Clear alarm and temperatureAlarm states, and read firmware version when the device is added", function() + test.socket.matter:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", alarm.alarm.off()) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", temperatureAlarm.temperatureAlarm.cleared()) + ) + + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.read_manufacturer_specific_attribute(mock_device, Basic.ID, DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR, DEVELCO_MANUFACTURER_CODE) + }) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "init and doConfigure lifecycles should be handled properly", + function() + test.socket.environment_update:__queue_receive({ "zigbee", { hub_zigbee_id = base64.encode(zigbee_test_utils.mock_hub_eui) } }) + test.socket.zigbee:__set_channel_ordering("relaxed") + + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + + test.wait_for_events() + + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", alarm.alarm.off()) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", temperatureAlarm.temperatureAlarm.cleared()) + ) + + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.read_manufacturer_specific_attribute(mock_device, Basic.ID, DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR, DEVELCO_MANUFACTURER_CODE) + }) + + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request( + mock_device, + zigbee_test_utils.mock_hub_eui, + PowerConfiguration.ID, + POWER_CONFIGURATION_ENDPOINT + ):to_endpoint(POWER_CONFIGURATION_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:configure_reporting( + mock_device, + 30, + 21600, + 1 + ):to_endpoint(POWER_CONFIGURATION_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request( + mock_device, + zigbee_test_utils.mock_hub_eui, + TemperatureMeasurement.ID, + TEMPERATURE_MEASUREMENT_ENDPOINT + ):to_endpoint(TEMPERATURE_MEASUREMENT_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting( + mock_device, + 60, + 600, + 100 + ):to_endpoint(TEMPERATURE_MEASUREMENT_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request( + mock_device, + zigbee_test_utils.mock_hub_eui, + IASZone.ID, + IASZONE_ENDPOINT + ):to_endpoint(IASZONE_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.attributes.ZoneStatus:configure_reporting( + mock_device, + 30, + 300, + 0 + ):to_endpoint(IASZONE_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.attributes.IASCIEAddress:write( + mock_device, + zigbee_test_utils.mock_hub_eui + ):to_endpoint(IASZONE_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.server.commands.ZoneEnrollResponse( + mock_device, + IasEnrollResponseCode.SUCCESS, + 0x00 + ) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.attributes.MaxDuration:write(mock_device, ALARM_DEFAULT_MAX_DURATION) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.read_manufacturer_specific_attribute(mock_device, Basic.ID, DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR, DEVELCO_MANUFACTURER_CODE) + }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.register_message_test( + "Battery voltage report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryVoltage:build_test_attr_report(mock_device, 24) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(14)) + } + } +) + +test.register_coroutine_test( + "ZoneStatusChangeNotification should be handled: detected", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0001, 0x00) + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.heat()) + ) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "ZoneStatusChangeNotification should be handled: tested", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0100, 0x01) + }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.heat()) + ) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "ZoneStatusChangeNotification should be handled: cleared", + function() + test.timer.__create_and_queue_test_time_advance_timer(6, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) + }) + + test.mock_time.advance_time(6) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared()) + ) + + test.wait_for_events() + end +) + +test.register_message_test( + "Temperature report should be handled (C) for the temperature cluster", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:build_test_attr_report(mock_device, 2500) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C" })) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_device.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } + } + } + } +) + +test.register_message_test( + "Refresh should read all necessary attributes", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + IASZone.attributes.ZoneStatus:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) + } + } + }, + { + inner_block_ordering = "relaxed" + } +) + +test.register_coroutine_test( + "infochanged to check for necessary preferences settings: tempSensitivity, warningDuration", + function() + local updates = { + preferences = { + tempSensitivity = 1.3, + warningDuration = 100 + } + } + + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) + + test.socket.zigbee:__expect_send({ + mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting( + mock_device, + 60, + 600, + 130 + )--:to_endpoint(TEMPERATURE_MEASUREMENT_ENDPOINT) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.attributes.MaxDuration:write(mock_device, 0x0064)--:to_endpoint(IASZONE_ENDPOINT) + }) + + + test.socket.zigbee:__set_channel_ordering("relaxed") + + end +) + +test.register_coroutine_test( + "Should detect older firmware version and use correct endian format to turn on the siren", + function() + -- Manually set the firmware version and endian format for testing + mock_device:set_field(PRIMARY_SW_VERSION, "040002", {persist = true}) + mock_device:set_field(SIREN_ENDIAN, "reverse", {persist = true}) + + -- Verify fields are set correctly + assert(mock_device:get_field(PRIMARY_SW_VERSION) < "040005", "PRIMARY_SW_VERSION should be less than '040005'") + assert(mock_device:get_field(SIREN_ENDIAN) == "reverse", "SIREN_ENDIAN should be set to 'reverse'") + + -- Test the siren command with reversed endian + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + + -- Expect the command with reverse endian format + local expectedConfiguration = SirenConfiguration(0x01) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning(mock_device, + expectedConfiguration, + data_types.Uint16(defaultWarningDuration), + data_types.Uint8(00), + data_types.Enum8(00)) + }) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Should detect newer firmware version and use correct endian format to turn on the siren", + function() + -- Manually set the firmware version and endian format for testing + mock_device:set_field(PRIMARY_SW_VERSION, "040005", {persist = true}) + mock_device:set_field(SIREN_ENDIAN, nil, {persist = true}) + + -- Verify fields are set correctly + assert(mock_device:get_field(PRIMARY_SW_VERSION) >= "040005", "PRIMARY_SW_VERSION should be greater than or equal to '040005'") + assert(mock_device:get_field(SIREN_ENDIAN) == nil, "SIREN_ENDIAN should be set to 'nil'") + + -- Test the siren command with reversed endian + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "siren", args = {} } + }) + + -- Expect the command with reverse endian format + local expectedConfiguration = SirenConfiguration(0x00) + expectedConfiguration:set_warning_mode(0x01) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning(mock_device, + expectedConfiguration, + data_types.Uint16(defaultWarningDuration), + data_types.Uint8(00), + data_types.Enum8(00)) + }) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Should detect older firmware version and use correct endian format to turn on the siren", + function() + -- Manually set the firmware version and endian format for testing + mock_device:set_field(PRIMARY_SW_VERSION, "040002", {persist = true}) + mock_device:set_field(SIREN_ENDIAN, "reverse", {persist = true}) + + -- Verify fields are set correctly + assert(mock_device:get_field(PRIMARY_SW_VERSION) < "040005", "PRIMARY_SW_VERSION should be less than '040005'") + assert(mock_device:get_field(SIREN_ENDIAN) == "reverse", "SIREN_ENDIAN should be set to 'reverse'") + + -- Test the siren command with reversed endian + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "off", args = {} } + }) + + -- Expect the command with reverse endian format + local expectedConfiguration = SirenConfiguration(0x00) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning(mock_device, + expectedConfiguration, + data_types.Uint16(0x00), + data_types.Uint8(00), + data_types.Enum8(00)) + }) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Should detect newer firmware version and use correct endian format to turn off the siren", + function() + -- Manually set the firmware version and endian format for testing + mock_device:set_field(PRIMARY_SW_VERSION, "040005", {persist = true}) + mock_device:set_field(SIREN_ENDIAN, nil, {persist = true}) + + -- Verify fields are set correctly + assert(mock_device:get_field(PRIMARY_SW_VERSION) >= "040005", "PRIMARY_SW_VERSION should be greater than or equal to '040005'") + assert(mock_device:get_field(SIREN_ENDIAN) == nil, "SIREN_ENDIAN should be set to 'nil'") + + -- Test the siren command with reversed endian + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "alarm", component = "main", command = "off", args = {} } + }) + + -- Expect the command with reverse endian format + local expectedConfiguration = SirenConfiguration(0x00) + expectedConfiguration:set_warning_mode(0x00) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.server.commands.StartWarning(mock_device, + expectedConfiguration, + data_types.Uint16(0x00), + data_types.Uint8(00), + data_types.Enum8(00)) + }) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Test firmware version conversion using direct simulation", + function() + -- Binary firmware version test cases + local test_cases = { + { + binary = string.char(4, 0, 5), -- \004\000\005 + expected_hex = "040005" -- Expected output + }, + { + binary = string.char(4, 0, 12), -- \004\000\012 + expected_hex = "04000c" -- Expected output + }, + { + binary = string.char(5, 1, 3), -- \005\001\003 + expected_hex = "050103" -- Expected output + } + } + + for i, test_case in ipairs(test_cases) do + print("\n----- Test Case " .. i .. " -----") + local binary_fw = test_case.binary + local expected_hex = test_case.expected_hex + + -- Print the raw binary version and its byte values + print("Binary firmware version (raw):", binary_fw) + print("Binary firmware bytes:", string.format( + "\\%03d\\%03d\\%03d", + string.byte(binary_fw, 1), + string.byte(binary_fw, 2), + string.byte(binary_fw, 3) + )) + + -- Reset the field for clean test + mock_device:set_field(PRIMARY_SW_VERSION, nil, {persist = true}) + + -- Create a mock value object + local mock_value = { + value = binary_fw + } + + -- Simulate what happens in primary_sw_version_attr_handler + local primary_sw_version = mock_value.value:gsub('.', function (c) + return string.format('%02x', string.byte(c)) + end) + + -- Store the version in PRIMARY_SW_VERSION field + mock_device:set_field(PRIMARY_SW_VERSION, primary_sw_version, {persist = true}) + + -- What the conversion should do + print("\nConversion steps:") + local hex_result = "" + for i = 1, #binary_fw do + local char = binary_fw:sub(i, i) + local byte_val = string.byte(char) + local hex_val = string.format('%02x', byte_val) + hex_result = hex_result .. hex_val + print(string.format("Character at position %d: byte value = %d, hex = %02x", + i, byte_val, byte_val)) + end + + print("\nExpected hex result:", expected_hex) + print("Manual conversion result:", hex_result) + + -- Verify the stored version + local stored_version = mock_device:get_field(PRIMARY_SW_VERSION) + print("\nStored version in device field:", stored_version) + assert(stored_version == expected_hex, + string.format("Version mismatch! Expected '%s' but got '%s'", + expected_hex, stored_version or "nil")) + end + end +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_smoke_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_smoke_detector.lua index ff4fa07184..574f08ae76 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_smoke_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_smoke_detector.lua @@ -258,6 +258,24 @@ test.register_message_test( } ) +test.register_coroutine_test( + "ZoneStatusChangeNotification should be handled: clear", + function() + test.timer.__create_and_queue_test_time_advance_timer(6, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) + }) + + test.mock_time.advance_time(6) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear()) + ) + + test.wait_for_events() + end +) + test.register_message_test( "Temperature report should be handled (C) for the temperature cluster", { diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index e449e1f194..a83204e5f6 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -116,6 +116,11 @@ zigbeeManufacturer: manufacturer: LUMI model: lumi.light.acn004 deviceProfileName: aqara-light + - id: "LUMI/lumi.light.cwacn1" + deviceLabel: Aqara Smart Dimmer Controller T1 (0-10v) + manufacturer: LUMI + model: lumi.light.cwacn1 + deviceProfileName: aqara-light - id: "Aqara/lumi.light.acn014" deviceLabel: Aqara LED Light Bulb T1 (Tunable White) manufacturer: Aqara @@ -500,6 +505,11 @@ zigbeeManufacturer: manufacturer: frient A/S model: SMRZB-342 deviceProfileName: frient-switch-power-energy-voltage + - id: "frient/IOMZB-110" + deviceLabel: frient IO Module + manufacturer: frient A/S + model: IOMZB-110 + deviceProfileName: switch-4inputs-2outputs - id: "AduroSmart Eria/AD-DimmableLight3001" deviceLabel: Eria Light manufacturer: AduroSmart Eria @@ -2359,16 +2369,58 @@ zigbeeManufacturer: model: NEXENTRO Dimming Actuator deviceProfileName: on-off-level # Inovelli + - id: "Inovelli/VZM30-SN" + deviceLabel: "Inovelli On/Off Blue Series" + manufacturer: Inovelli + model: VZM30-SN + deviceProfileName: inovelli-vzm30-sn - id: "Inovelli/VZM31-SN" deviceLabel: "Inovelli 2-in-1 Blue Series" manufacturer: Inovelli model: VZM31-SN deviceProfileName: inovelli-vzm31-sn + - id: "Inovelli/VZM32-SN" + deviceLabel: "Inovelli mmWave Dimmer Blue Series" + manufacturer: Inovelli + model: VZM32-SN + deviceProfileName: inovelli-vzm32-sn - id: "LAISIAO/BATH" deviceLabel: Laisiao Bathroom Heater manufacturer: LAISIAO model: yuba deviceProfileName: switch-smart-bath-heater-laisiao + # NodOn + - id: "NodOn/SIN-4-1-20" + deviceLabel: Zigbee Multifunction Relay Switch + manufacturer: NodOn + model: SIN-4-1-20 + deviceProfileName: basic-switch + - id: "NodOn/SIN-4-2-20" + deviceLabel: Zigbee ON/OFF Lighting Relay Switch + manufacturer: NodOn + model: SIN-4-2-20 + deviceProfileName: switch-level + - id: "NodOn/SIN-4-1-21" + deviceLabel: Zigbee Multifunction Relay Switch with Metering + manufacturer: NodOn + model: SIN-4-1-21 + deviceProfileName: switch-power-energy + #Yanmi + - id: "JNL/Y-K003-001" + deviceLabel: Yanmi Switch (3 Way) 1 + manufacturer: JNL + model: Y-K003-001 + deviceProfileName: basic-switch + - id: "JNL/Y-K001-001" + deviceLabel: Yanmi Switch (1 Way) + manufacturer: JNL + model: Y-K001-001 + deviceProfileName: basic-switch + - id: "JNL/Y-K002-001" + deviceLabel: Yanmi Switch (2 Way) 1 + manufacturer: JNL + model: Y-K002-001 + deviceProfileName: basic-switch zigbeeGeneric: - id: "genericSwitch" deviceLabel: Zigbee Switch diff --git a/drivers/SmartThings/zigbee-switch/profiles/aqara-light.yml b/drivers/SmartThings/zigbee-switch/profiles/aqara-light.yml index 3b4462bce2..6c2da08393 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/aqara-light.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/aqara-light.yml @@ -12,10 +12,6 @@ components: range: [1, 100] - id: colorTemperature version: 1 - config: - values: - - key: "colorTemperature.value" - range: [2700, 6000] - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/zigbee-switch/profiles/frient-io-output-switch.yml b/drivers/SmartThings/zigbee-switch/profiles/frient-io-output-switch.yml new file mode 100644 index 0000000000..551061d7b5 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/frient-io-output-switch.yml @@ -0,0 +1,25 @@ +name: frient-io-output-switch +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: refresh + version: 1 +preferences: + - title: "Output: On Time" + name: configOnTime + required: true + preferenceType: integer + definition: + minimum: 0 + maximum: 6553 + default: 0 + - title: "Output: Off Wait Time" + name: configOffWaitTime + required: true + preferenceType: integer + definition: + minimum: 0 + maximum: 6553 + default: 0 \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm30-sn.yml b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm30-sn.yml new file mode 100644 index 0000000000..ff670c0097 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm30-sn.yml @@ -0,0 +1,231 @@ +name: inovelli-vzm30-sn +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - id: temperatureMeasurement + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + - id: firmwareUpdate + version: 1 + categories: + - name: Switch + - id: button1 + label: Down Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button2 + label: Up Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button3 + label: Config Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController +preferences: + - name: "notificationChild" + title: "Add Child Device - Notification" + description: "Create Separate Child Device for Notification Control" + required: false + preferenceType: boolean + definition: + default: false + - name: "notificationType" + title: "Notification Effect" + description: "This is the notification effect used by the notification child device" + required: false + preferenceType: enumeration + definition: + options: + "255": "Clear" + "1": "Solid" + "2": "Fast Blink" + "3": "Slow Blink" + "4": "Pulse" + "5": "Chase" + "6": "Open/Close" + "7": "Small-to-Big" + "8": "Aurora" + "9": "Slow Falling" + "10": "Medium Falling" + "11": "Fast Falling" + "12": "Slow Rising" + "13": "Medium Rising" + "14": "Fast Rising" + "15": "Medium Blink" + "16": "Slow Chase" + "17": "Fast Chase" + "18": "Fast Siren" + "19": "Slow Siren" + default: 1 + - name: "parameter258" + title: "258. Switch Mode" + description: "Use as a Dimmer or an On/Off switch" + required: false + preferenceType: enumeration + definition: + options: + "0": "Dimmer" + "1": "On/Off (default)" + default: 1 + - name: "parameter22" + title: "22. Aux Switch Type" + description: "Set the Aux switch type. Smart Bulb Mode does not work in Dumb 3-Way Switch mode." + required: false + preferenceType: enumeration + definition: + options: + "0": "None" + "1": "3-Way Aux Switch (default)" + default: 1 + - name: "parameter52" + title: "52. Smart Bulb Mode" + description: "For use with Smart Bulbs that need constant power and are controlled via commands rather than power. Smart Bulb Mode does not work in Dumb 3-Way Switch mode." + required: false + preferenceType: enumeration + definition: + options: + "0": "Disabled (default)" + "1": "Smart Bulb Mode" + default: 0 + - name: "parameter1" + title: "1. Dimming Speed (Remote)" + description: "This changes the speed that the light dims up when controlled from the hub. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + Default=25 (2500ms or 2.5s)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 126 + default: 25 + - name: "parameter2" + title: "2. Dimming Speed (Local)" + description: "This changes the speed that the light dims up when controlled at the switch. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=127 (Sync with parameter 1)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 127 + default: 127 + - name: "parameter3" + title: "3. Ramp Rate (Remote)" + description: "This changes the speed that the light turns on when controlled from the hub. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=0" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 127 + default: 0 + - name: "parameter4" + title: "4. Ramp Rate (Local)" + description: "This changes the speed that the light turns on when controlled at the switch. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=127 (Sync with parameter 3)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 127 + default: 127 + - name: "parameter11" + title: "11. Invert Switch" + description: "Inverts the orientation of the switch. Useful when the switch is installed upside down. Essentially up becomes down and down becomes up." + required: false + preferenceType: enumeration + definition: + options: + "0": "No (default)" + "1": "Yes" + default: 0 + - name: "parameter15" + title: "15. Level After Power Restored" + description: "The level the switch will return to when power is restored after power failure. + 0=Off + 1-100=Set Level + 101=Use previous level." + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 101 + default: 101 + - name: "parameter95" + title: "95. LED Indicator Color (w/On)" + description: "Set the color of the Full LED Indicator when the load is on." + required: false + preferenceType: enumeration + definition: + options: + "0": "Red" + "7": "Orange" + "28": "Lemon" + "64": "Lime" + "85": "Green" + "106": "Teal" + "127": "Cyan" + "148": "Aqua" + "170": "Blue (default)" + "190": "Violet" + "212": "Magenta" + "234": "Pink" + "255": "White" + default: 170 + - name: "parameter96" + title: "96. LED Indicator Color (w/Off)" + description: "Set the color of the Full LED Indicator when the load is off." + required: false + preferenceType: enumeration + definition: + options: + "0": "Red" + "7": "Orange" + "28": "Lemon" + "64": "Lime" + "85": "Green" + "106": "Teal" + "127": "Cyan" + "148": "Aqua" + "170": "Blue (default)" + "190": "Violet" + "212": "Magenta" + "234": "Pink" + "255": "White" + default: 170 + - name: "parameter97" + title: "97. LED Indicator Intensity (w/On)" + description: "Set the intensity of the Full LED Indicator when the load is on." + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 100 + default: 50 + - name: "parameter98" + title: "98. LED Indicator Intensity (w/Off)" + description: "Set the intensity of the Full LED Indicator when the load is off." + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 100 + default: 5 \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml new file mode 100644 index 0000000000..746890a15a --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml @@ -0,0 +1,355 @@ +name: inovelli-vzm32-sn +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - id: motionSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + config: + values: + - key: "illuminance.value" + range: [0, 5000] + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + - id: firmwareUpdate + version: 1 + categories: + - name: Switch + - id: button1 + label: Down Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button2 + label: Up Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button3 + label: Config Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController +preferences: + - name: "notificationChild" + title: "Add Child Device - Notification" + description: "Create Separate Child Device for Notification Control" + required: false + preferenceType: boolean + definition: + default: false + - name: "notificationType" + title: "Notification Effect" + description: "This is the notification effect used by the notification child device" + required: false + preferenceType: enumeration + definition: + options: + "255": "Clear" + "1": "Solid" + "2": "Fast Blink" + "3": "Slow Blink" + "4": "Pulse" + "5": "Chase" + "6": "Open/Close" + "7": "Small-to-Big" + "8": "Aurora" + "9": "Slow Falling" + "10": "Medium Falling" + "11": "Fast Falling" + "12": "Slow Rising" + "13": "Medium Rising" + "14": "Fast Rising" + "15": "Medium Blink" + "16": "Slow Chase" + "17": "Fast Chase" + "18": "Fast Siren" + "19": "Slow Siren" + default: 1 + - name: "parameter258" + title: "258. Switch Mode" + description: "Use as a Dimmer or an On/Off switch" + required: false + preferenceType: enumeration + definition: + options: + "0": "Dimmer (default)" + "1": "On/Off" + default: 0 + - name: "parameter52" + title: "52. Smart Bulb Mode" + description: "For use with Smart Bulbs that need constant power and are controlled via commands rather than power. Smart Bulb Mode does not work in Dumb 3-Way Switch mode." + required: false + preferenceType: enumeration + definition: + options: + "0": "Disabled (default)" + "1": "Smart Bulb Mode" + default: 0 + - name: "parameter1" + title: "1. Dimming Speed (Remote)" + description: "This changes the speed that the light dims up when controlled from the hub. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + Default=25 (2500ms or 2.5s)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 126 + default: 25 + - name: "parameter2" + title: "2. Dimming Speed (Local)" + description: "This changes the speed that the light dims up when controlled at the switch. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=127 (Sync with parameter 1)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 127 + default: 127 + - name: "parameter3" + title: "3. Ramp Rate (Remote)" + description: "This changes the speed that the light turns on when controlled from the hub. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=127 (Sync with parameter 1)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 127 + default: 127 + - name: "parameter4" + title: "4. Ramp Rate (Local)" + description: "This changes the speed that the light turns on when controlled at the switch. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=127 (Sync with parameter 3)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 127 + default: 127 + - name: "parameter9" + title: "9. Minimum Level" + description: "The minimum level that the light can be dimmed. Useful when the user has a light that does not turn on or flickers at a lower level." + required: false + preferenceType: number + definition: + minimum: 1 + maximum: 99 + default: 1 + - name: "parameter10" + title: "10. Maximum Level" + description: "The maximum level that the light can be dimmed. Useful when the user wants to limit the maximum brighness." + required: false + preferenceType: number + definition: + minimum: 2 + maximum: 100 + default: 100 + - name: "parameter15" + title: "15. Level After Power Restored" + description: "The level the switch will return to when power is restored after power failure. + 0=Off + 1-100=Set Level + 101=Use previous level." + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 101 + default: 101 + - name: "parameter95" + title: "95. LED Indicator Color (w/On)" + description: "Set the color of the Full LED Indicator when the load is on." + required: false + preferenceType: enumeration + definition: + options: + "0": "Red" + "7": "Orange" + "28": "Lemon" + "64": "Lime" + "85": "Green" + "106": "Teal" + "127": "Cyan" + "148": "Aqua" + "170": "Blue (default)" + "190": "Violet" + "212": "Magenta" + "234": "Pink" + "255": "White" + default: 170 + - name: "parameter96" + title: "96. LED Indicator Color (w/Off)" + description: "Set the color of the Full LED Indicator when the load is off." + required: false + preferenceType: enumeration + definition: + options: + "0": "Red" + "7": "Orange" + "28": "Lemon" + "64": "Lime" + "85": "Green" + "106": "Teal" + "127": "Cyan" + "148": "Aqua" + "170": "Blue (default)" + "190": "Violet" + "212": "Magenta" + "234": "Pink" + "255": "White" + default: 170 + - name: "parameter97" + title: "97. LED Indicator Intensity (w/On)" + description: "Set the intensity of the Full LED Indicator when the load is on." + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 100 + default: 50 + - name: "parameter98" + title: "98. LED Indicator Intensity (w/Off)" + description: "Set the intensity of the Full LED Indicator when the load is off." + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 100 + default: 5 + - name: "parameter101" + title: "101. mmWave Height Minimum (Floor)" + description: "Minimum range of the Z-Axis in cm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: -300 + - name: "parameter102" + title: "102. mmWave Height Maximum (Ceiling)" + description: "Maximum range of the Z-Axis in cm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: 300 + - name: "parameter103" + title: "103. mmWave Width Minimum (Left)" + description: "Minimum range of the X-Axis in cm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: -600 + - name: "parameter104" + title: "104. mmWave Width Maximum (Right)" + description: "Maximum range of the X-Axis in cm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: 600 + - name: "parameter105" + title: "105. mmWave Depth Minimum (Near)" + description: "Minimum range of the Y-Axis in cm" + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 600 + default: 0 + - name: "parameter106" + title: "106. mmWave Depth Maximum (Far)" + description: "Maximum range of the Y-Axis in cm" + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 600 + default: 600 + - name: "parameter110" + title: "110. Light On Presence Behavior" + description: "When presence is detected, choose how to control the light load" + required: true + preferenceType: enumeration + definition: + options: + "0": "Disabled" + "1": "Auto On/Off when occupied (default)" + "2": "Auto Off when vacant" + "3": "Auto On when occupied" + "4": "Auto On/Off when Vacant" + "5": "Auto On when Vacant" + "6": "Auto Off when Occupied" + default: 1 + - name: "parameter111" + title: "111. mmWave Control Commands" + description: "Advanced commands to send to the mmWave Module (Please see documentation)" + required: false + preferenceType: enumeration + definition: + options: + "1": "Set Interference Area" + "3": "Clear Interference Area" + "0": "Factory Reset Module" + default: 3 + - name: "parameter112" + title: "112. mmWave Detection Sensitivity" + description: "Adjust the sensitivity of the mmWave sensor. 0-Low, 1-Medium, 2-High." + required: false + preferenceType: enumeration + definition: + options: + "0": "Low" + "1": "Medium" + "2": "High (default)" + default: 2 + - name: "parameter113" + title: "113. mmWave Detection Delay" + description: "The time from detecting a person to triggering an action. 0-Low (5s), 1-Medium (1s), 2-Fast (0.2s)." + required: false + preferenceType: enumeration + definition: + options: + "0": "5 seconds" + "1": "1 second" + "2": "0.2 seconds (default)" + default: 2 + - name: "parameter114" + title: "114. mmWave Time Out" + description: "Adjust the timeout after presence is no longer detected. After the timeout the load will turn off." + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 4294967295 + default: 30 + - name: "parameter34" + title: "34. OTA Image Type" + description: "Which endpoint should the switch advertise for OTA update (Zigbee, mmWave, or both)." + required: true + preferenceType: enumeration + definition: + options: + "0": "Zigbee (default)" + "1": "mmWave" + "2": "Alternating" + default: 0 diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-4inputs-2outputs.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-4inputs-2outputs.yml new file mode 100644 index 0000000000..c6120b2561 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-4inputs-2outputs.yml @@ -0,0 +1,107 @@ +name: switch-4inputs-2outputs +components: + - id: main + capabilities: + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Switch + - id: input1 + label: "Input 1" + capabilities: + - id: switch + version: 1 + - id: input2 + label: "Input 2" + capabilities: + - id: switch + version: 1 + - id: input3 + label: "Input 3" + capabilities: + - id: switch + version: 1 + - id: input4 + label: "Input 4" + capabilities: + - id: switch + version: 1 +preferences: + # Input 1 + - title: "Input 1: Reverse Polarity" + name: reversePolarity1 + required: true + preferenceType: boolean + definition: + default: false + - title: "Input 1: Control Output 1" + name: controlOutput11 + required: true + preferenceType: boolean + definition: + default: false + - title: "Input 1: Control Output 2" + name: controlOutput21 + required: true + preferenceType: boolean + definition: + default: false + # Input 2 + - title: "Input 2: Reverse Polarity" + name: reversePolarity2 + required: true + preferenceType: boolean + definition: + default: false + - title: "Input 2: Control Output 1" + name: controlOutput12 + required: true + preferenceType: boolean + definition: + default: false + - title: "Input 2: Control Output 2" + name: controlOutput22 + required: true + preferenceType: boolean + definition: + default: false + # Input 3 + - title: "Input 3: Reverse Polarity" + name: reversePolarity3 + required: true + preferenceType: boolean + definition: + default: false + - title: "Input 3: Control Output 1" + name: controlOutput13 + required: true + preferenceType: boolean + definition: + default: false + - title: "Input 3: Control Output 2" + name: controlOutput23 + required: true + preferenceType: boolean + definition: + default: false + # Input 4 + - title: "Input 4: Reverse Polarity" + name: reversePolarity4 + required: true + preferenceType: boolean + definition: + default: false + - title: "Input 4: Control Output 1" + name: controlOutput14 + required: true + preferenceType: boolean + definition: + default: false + - title: "Input 4: Control Output 2" + name: controlOutput24 + required: true + preferenceType: boolean + definition: + default: false \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/src/aqara-light/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/aqara-light/can_handle.lua new file mode 100644 index 0000000000..7b20caf5c7 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/aqara-light/can_handle.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local FINGERPRINTS = { + { mfr = "LUMI", model = "lumi.light.acn004" }, + { mfr = "Aqara", model = "lumi.light.acn014" }, + { mfr = "LUMI", model = "lumi.light.cwacn1" } + } + + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("aqara-light") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua b/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua index 3009cb6d0c..06d4bf1cc7 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" @@ -12,30 +15,27 @@ local PRIVATE_CLUSTER_ID = 0xFCC0 local PRIVATE_ATTRIBUTE_ID = 0x0009 local MFG_CODE = 0x115F -local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.light.acn004" }, - { mfr = "Aqara", model = "lumi.light.acn014" } -} - -local function is_aqara_products(opts, driver, device) - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("aqara-light") - return true, subdriver - end - end - return false -end - local function do_refresh(self, device) device:send(OnOff.attributes.OnOff:read(device)) device:send(Level.attributes.CurrentLevel:read(device)) device:send(ColorControl.attributes.ColorTemperatureMireds:read(device)) end +local function emit_event_if_latest_state_missing(device, component, capability, attribute_name, value) + if device:get_latest_state(component, capability.ID, attribute_name) == nil then + device:emit_event(value) + end +end + local function device_added(driver, device, event) device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1)) -- private + + local value = { minimum = 2700, maximum = 6000 } + if device:get_model() == "lumi.light.cwacn1" then + value.maximum = 6500 + end + emit_event_if_latest_state_missing(device, "main", capabilities.colorTemperature, capabilities.colorTemperature.colorTemperatureRange.NAME, capabilities.colorTemperature.colorTemperatureRange(value)) end local function do_configure(self, device) @@ -54,9 +54,18 @@ local function set_level_handler(driver, device, cmd) device:send(Level.commands.MoveToLevelWithOnOff(device, level, dimming_rate)) end +local function init(self, device) + local value = { minimum = 2700, maximum = 6000 } + if device:get_model() == "lumi.light.cwacn1" then + value.maximum = 6500 + end + emit_event_if_latest_state_missing(device, "main", capabilities.colorTemperature, capabilities.colorTemperature.colorTemperatureRange.NAME, capabilities.colorTemperature.colorTemperatureRange(value)) +end + local aqara_light_handler = { NAME = "Aqara Light Handler", lifecycle_handlers = { + init = init, added = device_added, doConfigure = do_configure }, @@ -65,7 +74,7 @@ local aqara_light_handler = { [capabilities.switchLevel.commands.setLevel.NAME] = set_level_handler } }, - can_handle = is_aqara_products + can_handle = require("aqara-light.can_handle"), } return aqara_light_handler diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/aqara/can_handle.lua new file mode 100644 index 0000000000..e183f78266 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/aqara/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local FINGERPRINTS = require("aqara.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("aqara") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/aqara/fingerprints.lua new file mode 100644 index 0000000000..63f94e2be3 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/aqara/fingerprints.lua @@ -0,0 +1,22 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "LUMI", model = "lumi.plug.maeu01" }, + { mfr = "LUMI", model = "lumi.plug.macn01" }, + { mfr = "LUMI", model = "lumi.switch.n0agl1" }, + { mfr = "LUMI", model = "lumi.switch.l0agl1" }, + { mfr = "LUMI", model = "lumi.switch.n0acn2" }, + { mfr = "LUMI", model = "lumi.switch.n1acn1" }, + { mfr = "LUMI", model = "lumi.switch.n2acn1" }, + { mfr = "LUMI", model = "lumi.switch.n3acn1" }, + { mfr = "LUMI", model = "lumi.switch.b1laus01" }, + { mfr = "LUMI", model = "lumi.switch.b2laus01" }, + { mfr = "LUMI", model = "lumi.switch.n1aeu1" }, + { mfr = "LUMI", model = "lumi.switch.n2aeu1" }, + { mfr = "LUMI", model = "lumi.switch.l1aeu1" }, + { mfr = "LUMI", model = "lumi.switch.l2aeu1" }, + { mfr = "LUMI", model = "lumi.switch.b1nacn01" }, + { mfr = "LUMI", model = "lumi.switch.b2nacn01" }, + { mfr = "LUMI", model = "lumi.switch.b3n01" } + } diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/init.lua b/drivers/SmartThings/zigbee-switch/src/aqara/init.lua index 6558f7f282..0c64b594fe 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" @@ -24,26 +27,6 @@ local ELECTRIC_SWITCH_TYPE_ATTRIBUTE_ID = 0x000A local LAST_REPORT_TIME = "LAST_REPORT_TIME" local PRIVATE_MODE = "PRIVATE_MODE" -local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.plug.maeu01" }, - { mfr = "LUMI", model = "lumi.plug.macn01" }, - { mfr = "LUMI", model = "lumi.switch.n0agl1" }, - { mfr = "LUMI", model = "lumi.switch.l0agl1" }, - { mfr = "LUMI", model = "lumi.switch.n0acn2" }, - { mfr = "LUMI", model = "lumi.switch.n1acn1" }, - { mfr = "LUMI", model = "lumi.switch.n2acn1" }, - { mfr = "LUMI", model = "lumi.switch.n3acn1" }, - { mfr = "LUMI", model = "lumi.switch.b1laus01" }, - { mfr = "LUMI", model = "lumi.switch.b2laus01" }, - { mfr = "LUMI", model = "lumi.switch.n1aeu1" }, - { mfr = "LUMI", model = "lumi.switch.n2aeu1" }, - { mfr = "LUMI", model = "lumi.switch.l1aeu1" }, - { mfr = "LUMI", model = "lumi.switch.l2aeu1" }, - { mfr = "LUMI", model = "lumi.switch.b1nacn01" }, - { mfr = "LUMI", model = "lumi.switch.b2nacn01" }, - { mfr = "LUMI", model = "lumi.switch.b3n01" } -} - local preference_map = { ["stse.restorePowerState"] = { cluster_id = PRIVATE_CLUSTER_ID, @@ -137,15 +120,6 @@ local preference_map = { }, } -local function is_aqara_products(opts, driver, device) - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("aqara") - return true, subdriver - end - end - return false -end local function private_mode_handler(driver, device, value, zb_rx) device:set_field(PRIVATE_MODE, value.value, { persist = true }) @@ -286,7 +260,7 @@ local aqara_switch_handler = { require("aqara.version"), require("aqara.multi-switch") }, - can_handle = is_aqara_products + can_handle = require("aqara.can_handle"), } return aqara_switch_handler diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/can_handle.lua new file mode 100644 index 0000000000..1524296cff --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local FINGERPRINTS = require("aqara.multi-switch.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("aqara.multi-switch") + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/fingerprints.lua new file mode 100644 index 0000000000..71ed1b26b3 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/fingerprints.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "LUMI", model = "lumi.switch.n1acn1", children = 1, child_profile = "" }, + { mfr = "LUMI", model = "lumi.switch.n2acn1", children = 2, child_profile = "aqara-switch-child" }, + { mfr = "LUMI", model = "lumi.switch.n3acn1", children = 3, child_profile = "aqara-switch-child" }, + { mfr = "LUMI", model = "lumi.switch.b1laus01", children = 1, child_profile = "" }, + { mfr = "LUMI", model = "lumi.switch.b2laus01", children = 2, child_profile = "aqara-switch-child" }, + { mfr = "LUMI", model = "lumi.switch.l2aeu1", children = 2, child_profile = "aqara-switch-child" }, + { mfr = "LUMI", model = "lumi.switch.n2aeu1", children = 2, child_profile = "aqara-switch-child" }, + { mfr = "LUMI", model = "lumi.switch.b2nacn01", children = 2, child_profile = "aqara-switch-child" }, + { mfr = "LUMI", model = "lumi.switch.b3n01", children = 3, child_profile = "aqara-switch-child" } +} diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/init.lua b/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/init.lua index 4e54b67e2f..9280a30d0d 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local device_lib = require "st.device" local capabilities = require "st.capabilities" local cluster_base = require "st.zigbee.cluster_base" @@ -8,27 +11,7 @@ local switch_utils = require "switch_utils" local PRIVATE_CLUSTER_ID = 0xFCC0 local PRIVATE_ATTRIBUTE_ID = 0x0009 local MFG_CODE = 0x115F - -local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.switch.n1acn1", children = 1, child_profile = "" }, - { mfr = "LUMI", model = "lumi.switch.n2acn1", children = 2, child_profile = "aqara-switch-child" }, - { mfr = "LUMI", model = "lumi.switch.n3acn1", children = 3, child_profile = "aqara-switch-child" }, - { mfr = "LUMI", model = "lumi.switch.b1laus01", children = 1, child_profile = "" }, - { mfr = "LUMI", model = "lumi.switch.b2laus01", children = 2, child_profile = "aqara-switch-child" }, - { mfr = "LUMI", model = "lumi.switch.l2aeu1", children = 2, child_profile = "aqara-switch-child" }, - { mfr = "LUMI", model = "lumi.switch.n2aeu1", children = 2, child_profile = "aqara-switch-child" }, - { mfr = "LUMI", model = "lumi.switch.b2nacn01", children = 2, child_profile = "aqara-switch-child" }, - { mfr = "LUMI", model = "lumi.switch.b3n01", children = 3, child_profile = "aqara-switch-child" } -} - -local function is_aqara_products(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 FINGERPRINTS = require("aqara.multi-switch.fingerprints") local function get_children_amount(device) for _, fingerprint in ipairs(FINGERPRINTS) do @@ -106,7 +89,7 @@ local aqara_multi_switch_handler = { init = configurations.power_reconfig_wrapper(device_init), added = device_added }, - can_handle = is_aqara_products + can_handle = require("aqara.multi-switch.can_handle"), } return aqara_multi_switch_handler diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/aqara/sub_drivers.lua new file mode 100644 index 0000000000..d064785cb4 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/aqara/sub_drivers.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load = require "lazy_load_subdriver" + +return { + lazy_load("aqara.multi-switch"), + lazy_load("aqara.version"), +} diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/version/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/aqara/version/can_handle.lua new file mode 100644 index 0000000000..d56744fefa --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/aqara/version/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function (opts, driver, device) + local PRIVATE_MODE = "PRIVATE_MODE" + local private_mode = device:get_field(PRIVATE_MODE) or 0 + local res = private_mode == 1 + if res then + return res, require("aqara.version") + else + return res + end +end diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/version/init.lua b/drivers/SmartThings/zigbee-switch/src/aqara/version/init.lua index 01767da106..bca3e9fb88 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara/version/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara/version/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" local constants = require "st.zigbee.constants" @@ -102,10 +105,7 @@ local aqara_switch_version_handler = { } } }, - can_handle = function (opts, driver, device) - local private_mode = device:get_field(PRIVATE_MODE) or 0 - return private_mode == 1 - end + can_handle = require("aqara.version.can_handle") } return aqara_switch_version_handler diff --git a/drivers/SmartThings/zigbee-switch/src/bad_on_off_data_type/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/bad_on_off_data_type/can_handle.lua new file mode 100644 index 0000000000..49b42ddb35 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/bad_on_off_data_type/can_handle.lua @@ -0,0 +1,24 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +-- There are reports of at least one device (SONOFF 01MINIZB) which occasionally +-- reports this value as an Int8, rather than a Boolean, as per the spec +return function(opts, driver, device, zb_rx, ...) + local zcl_clusters = require "st.zigbee.zcl.clusters" + local data_types = require "st.zigbee.data_types" + local can_handle = opts.dispatcher_class == "ZigbeeMessageDispatcher" and + device:get_manufacturer() == "SONOFF" and + zb_rx.body and + zb_rx.body.zcl_body and + zb_rx.body.zcl_body.attr_records and + zb_rx.address_header.cluster.value == zcl_clusters.OnOff.ID and + zb_rx.body.zcl_body.attr_records[1].attr_id.value == zcl_clusters.OnOff.attributes.OnOff.ID and + zb_rx.body.zcl_body.attr_records[1].data_type.value ~= data_types.Boolean.ID + if can_handle then + local subdriver = require("bad_on_off_data_type") + return true, subdriver + else + return false + end +end diff --git a/drivers/SmartThings/zigbee-switch/src/bad_on_off_data_type/init.lua b/drivers/SmartThings/zigbee-switch/src/bad_on_off_data_type/init.lua index ee0c82977e..2a59166bbb 100644 --- a/drivers/SmartThings/zigbee-switch/src/bad_on_off_data_type/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/bad_on_off_data_type/init.lua @@ -1,40 +1,9 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local zcl_clusters = require "st.zigbee.zcl.clusters" -local data_types = require "st.zigbee.data_types" local capabilities = require "st.capabilities" --- There are reports of at least one device (SONOFF 01MINIZB) which occasionally --- reports this value as an Int8, rather than a Boolean, as per the spec -local function incorrect_data_type_detected(opts, driver, device, zb_rx, ...) - local can_handle = opts.dispatcher_class == "ZigbeeMessageDispatcher" and - device:get_manufacturer() == "SONOFF" and - zb_rx.body and - zb_rx.body.zcl_body and - zb_rx.body.zcl_body.attr_records and - zb_rx.address_header.cluster.value == zcl_clusters.OnOff.ID and - zb_rx.body.zcl_body.attr_records[1].attr_id.value == zcl_clusters.OnOff.attributes.OnOff.ID and - zb_rx.body.zcl_body.attr_records[1].data_type.value ~= data_types.Boolean.ID - if can_handle then - local subdriver = require("bad_on_off_data_type") - return true, subdriver - else - return false - end -end - local function on_off_attr_handler(driver, device, value, zb_rx) local attr = capabilities.switch.switch device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, value.value == 0 and attr.off() or attr.on()) @@ -49,7 +18,7 @@ local bad_on_off_data_type = { } } }, - can_handle = incorrect_data_type_detected + can_handle = require("bad_on_off_data_type.can_handle"), } -return bad_on_off_data_type \ No newline at end of file +return bad_on_off_data_type diff --git a/drivers/SmartThings/zigbee-switch/src/configurations/devices.lua b/drivers/SmartThings/zigbee-switch/src/configurations/devices.lua index 51fd1c3665..d49d10e9c5 100644 --- a/drivers/SmartThings/zigbee-switch/src/configurations/devices.lua +++ b/drivers/SmartThings/zigbee-switch/src/configurations/devices.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.zigbee.zcl.clusters" local ColorControl = clusters.ColorControl @@ -18,7 +7,10 @@ local IASZone = clusters.IASZone local ElectricalMeasurement = clusters.ElectricalMeasurement local SimpleMetering = clusters.SimpleMetering local Alarms = clusters.Alarms +local BasicInput = clusters.BasicInput +local OnOff = clusters.OnOff local constants = require "st.zigbee.constants" +local data_types = require "st.zigbee.data_types" local devices = { IKEA_RGB_BULB = { @@ -121,6 +113,64 @@ local devices = { }, } }, + FRIENT_IO_MODULE = { + FINGERPRINTS = { + { mfr = "frient A/S", model = "IOMZB-110" } + }, + CONFIGURATION = { + { + cluster = OnOff.ID, + attribute = OnOff.attributes.OnTime.ID, + minimum_interval = 10, + maximum_interval = 600, + reportable_change = 1, + data_type = OnOff.attributes.OnOff.base_type, + configurable = true, + monitored = true + }, + { + cluster = OnOff.ID, + attribute = OnOff.attributes.OffWaitTime.ID, + minimum_interval = 10, + maximum_interval = 600, + reportable_change = 1, + data_type = OnOff.attributes.OffWaitTime.base_type, + configurable = true, + monitored = true + }, + { + cluster = BasicInput.ID, + attribute = BasicInput.attributes.PresentValue.ID, + minimum_interval = 10, + maximum_interval = 600, + reportable_change = 0, + data_type = BasicInput.attributes.PresentValue.base_type, + configurable = true, + monitored = true + }, + { + cluster = BasicInput.ID, + attribute = BasicInput.attributes.Polarity.ID, + minimum_interval = 10, + maximum_interval = 600, + reportable_change = 0, + data_type = BasicInput.attributes.Polarity.base_type, + configurable = true, + monitored = true + }, + { + cluster = BasicInput.ID, + attribute = 0x8000, -- IASActivation + minimum_interval = 10, + maximum_interval = 600, + reportable_change = 0, + data_type = data_types.Uint16, + mfg_code = 0x1015, + configurable = true, + monitored = true + } + } + } } return devices \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/src/configurations/init.lua b/drivers/SmartThings/zigbee-switch/src/configurations/init.lua index 2e1c4e2fc9..01f42abf91 100644 --- a/drivers/SmartThings/zigbee-switch/src/configurations/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/configurations/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local CONFIGURATION_VERSION_KEY = "_configuration_version" diff --git a/drivers/SmartThings/zigbee-switch/src/ezex/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/ezex/can_handle.lua new file mode 100644 index 0000000000..4bb61719ff --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/ezex/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local ZIGBEE_METERING_SWITCH_FINGERPRINTS = { + { model = "E240-KR116Z-HA" } + } + + for _, fingerprint in ipairs(ZIGBEE_METERING_SWITCH_FINGERPRINTS) do + if device:get_model() == fingerprint.model then + local subdriver = require("ezex") + return true, subdriver + end + end + + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/ezex/init.lua b/drivers/SmartThings/zigbee-switch/src/ezex/init.lua index f88c39bb75..6c2d9e45e3 100644 --- a/drivers/SmartThings/zigbee-switch/src/ezex/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/ezex/init.lua @@ -1,35 +1,9 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local zigbee_constants = require "st.zigbee.constants" local configurations = require "configurations" -local ZIGBEE_METERING_SWITCH_FINGERPRINTS = { - { model = "E240-KR116Z-HA" } -} - -local is_zigbee_ezex_switch = function(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_METERING_SWITCH_FINGERPRINTS) do - if device:get_model() == fingerprint.model then - local subdriver = require("ezex") - return true, subdriver - end - end - - return false -end - local do_init = function(self, device) device:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, 1000000, {persist = true}) device:set_field(zigbee_constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, 1000, {persist = true}) @@ -40,7 +14,7 @@ local ezex_switch_handler = { lifecycle_handlers = { init = configurations.power_reconfig_wrapper(do_init) }, - can_handle = is_zigbee_ezex_switch + can_handle = require("ezex.can_handle"), } return ezex_switch_handler diff --git a/drivers/SmartThings/zigbee-switch/src/frient-IO/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/frient-IO/can_handle.lua new file mode 100644 index 0000000000..4e1d465ee3 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/frient-IO/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Function to determine if the driver can handle this device +return function(opts, driver, device, ...) + if device:get_manufacturer() == "frient A/S" and device:get_model() == "IOMZB-110" then + local subdriver = require("frient-IO") + return true, subdriver + else + return false + end +end diff --git a/drivers/SmartThings/zigbee-switch/src/frient-IO/init.lua b/drivers/SmartThings/zigbee-switch/src/frient-IO/init.lua new file mode 100644 index 0000000000..293583f442 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/frient-IO/init.lua @@ -0,0 +1,488 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Zigbee Spec Utils +local constants = require "st.zigbee.constants" +local messages = require "st.zigbee.messages" +local zdo_messages = require "st.zigbee.zdo" +local bind_request = require "st.zigbee.zdo.bind_request" +local unbind_request = require "frient-IO.unbind_request" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local zcl_global_commands = require "st.zigbee.zcl.global_commands" +local Status = require "st.zigbee.generated.types.ZclStatus" + +local clusters = require "st.zigbee.zcl.clusters" +local BasicInput = clusters.BasicInput +local OnOff = clusters.OnOff +-- Capabilities +local capabilities = require "st.capabilities" +local Switch = capabilities.switch +local CHILD_OUTPUT_PROFILE = "frient-io-output-switch" +local utils = require "st.utils" + +local configurationMap = require "configurations" + +local COMPONENTS = { + INPUT_1 = "input1", + INPUT_2 = "input2", + INPUT_3 = "input3", + INPUT_4 = "input4", + OUTPUT_1 = "output1", + OUTPUT_2 = "output2" +} + +local ZIGBEE_ENDPOINTS = { + INPUT_1 = 0x70, + INPUT_2 = 0x71, + INPUT_3 = 0x72, + INPUT_4 = 0x73, + OUTPUT_1 = 0x74, + OUTPUT_2 = 0x75 +} + +local INPUT_CONFIGS = { + { + endpoint = ZIGBEE_ENDPOINTS.INPUT_1, + reverse_pref = "reversePolarity1", + binds = { + { pref = "controlOutput11", endpoint = ZIGBEE_ENDPOINTS.OUTPUT_1 }, + { pref = "controlOutput21", endpoint = ZIGBEE_ENDPOINTS.OUTPUT_2 } + } + }, + { + endpoint = ZIGBEE_ENDPOINTS.INPUT_2, + reverse_pref = "reversePolarity2", + binds = { + { pref = "controlOutput12", endpoint = ZIGBEE_ENDPOINTS.OUTPUT_1 }, + { pref = "controlOutput22", endpoint = ZIGBEE_ENDPOINTS.OUTPUT_2 } + } + }, + { + endpoint = ZIGBEE_ENDPOINTS.INPUT_3, + reverse_pref = "reversePolarity3", + binds = { + { pref = "controlOutput13", endpoint = ZIGBEE_ENDPOINTS.OUTPUT_1 }, + { pref = "controlOutput23", endpoint = ZIGBEE_ENDPOINTS.OUTPUT_2 } + } + }, + { + endpoint = ZIGBEE_ENDPOINTS.INPUT_4, + reverse_pref = "reversePolarity4", + binds = { + { pref = "controlOutput14", endpoint = ZIGBEE_ENDPOINTS.OUTPUT_1 }, + { pref = "controlOutput24", endpoint = ZIGBEE_ENDPOINTS.OUTPUT_2 } + } + } +} + +local OUTPUT_INFO = { + ["1"] = { endpoint = ZIGBEE_ENDPOINTS.OUTPUT_1, key = "frient-io-output-1", label_suffix = "Output 1" }, + ["2"] = { endpoint = ZIGBEE_ENDPOINTS.OUTPUT_2, key = "frient-io-output-2", label_suffix = "Output 2" } +} + +local OUTPUT_BY_ENDPOINT, OUTPUT_BY_KEY = {}, {} +for suffix, info in pairs(OUTPUT_INFO) do + info.suffix = suffix + OUTPUT_BY_ENDPOINT[info.endpoint] = info + OUTPUT_BY_KEY[info.key] = info +end + +local ZIGBEE_MFG_CODES = { + Develco = 0x1015 +} + +local ZIGBEE_MFG_ATTRIBUTES = { + client = { + OnWithTimeOff_OnTime = { + ID = 0x8000, + data_type = data_types.Uint16 + }, + OnWithTimeOff_OffWaitTime = { + ID = 0x8001, + data_type = data_types.Uint16 + } + }, + server = { IASActivation = { + ID = 0x8000, + data_type = data_types.Uint16 + } } +} + +local function write_client_manufacturer_specific_attribute(device, cluster_id, attr_id, mfg_specific_code, data_type, + payload) + local message = cluster_base.write_manufacturer_specific_attribute(device, cluster_id, attr_id, mfg_specific_code, + data_type, payload) + + message.body.zcl_header.frame_ctrl:set_direction_client() + return message +end + +local function write_basic_input_polarity_attr(device, ep_id, payload) + local value = data_types.validate_or_build_type(payload and 1 or 0, + BasicInput.attributes.Polarity.base_type, + "payload") + device:send(cluster_base.write_attribute(device, data_types.ClusterId(BasicInput.ID), + data_types.AttributeId(BasicInput.attributes.Polarity.ID), + value):to_endpoint(ep_id)) +end + +local function ensure_child_devices(driver, device) + if device.parent_assigned_child_key ~= nil then + return + end + + for _, info in pairs(OUTPUT_INFO) do + local child = device:get_child_by_parent_assigned_key(info.key) + if child == nil then + driver:try_create_device({ + type = "EDGE_CHILD", + parent_device_id = device.id, + parent_assigned_child_key = info.key, + profile = CHILD_OUTPUT_PROFILE, + label = string.format("%s %s", device.label, info.label_suffix), + vendor_provided_label = info.label_suffix + }) + child = device:get_child_by_parent_assigned_key(info.key) + end + if child then + child:set_field("endpoint", info.endpoint, { persist = true }) + end + end +end + +local function sanitize_timing(value) + local int = math.tointeger(value) or 0 + return utils.clamp_value(int, 0, 0xFFFF) +end + +local function get_output_timing(device, suffix) + local info = OUTPUT_INFO[suffix] + if not info then return 0, 0 end + local child = device:get_child_by_parent_assigned_key(info.key) + local on_time = math.floor((sanitize_timing(device.preferences["configOnTime" .. suffix]))*10) + local off_wait = math.floor((sanitize_timing(device.preferences["configOffWaitTime" .. suffix]))*10) + if child then + on_time = math.floor((sanitize_timing(child.preferences.configOnTime)) * 10) + off_wait = math.floor((sanitize_timing(child.preferences.configOffWaitTime)) * 10) + end + return on_time, off_wait +end + +local function handle_output_command(device, suffix, command_name) + local info = OUTPUT_INFO[suffix] + if info == nil then return end + local config_on_time, config_off_wait_time = get_output_timing(device, suffix) + local endpoint = info.endpoint + + if command_name == "on" then + if config_on_time == 0 then + device:send(OnOff.server.commands.On(device):to_endpoint(endpoint)) + else + device:send(OnOff.server.commands.OnWithTimedOff(device, data_types.Uint8(0), + data_types.Uint16(config_on_time), data_types.Uint16(config_off_wait_time)):to_endpoint(endpoint)) + end + else + device:send(OnOff.server.commands.Off(device):to_endpoint(endpoint)) + end +end + +local function emit_switch_event_for_endpoint(device, endpoint, event) + local info = OUTPUT_BY_ENDPOINT[endpoint] + if info ~= nil then + local child = device:get_child_by_parent_assigned_key(info.key) + if child then + child:emit_event(event) + return + end + end + device:emit_event_for_endpoint(endpoint, event) +end + +local function register_native_switch_handler(device, endpoint) + local field_key = string.format("frient_io_native_%02X", endpoint) + local info = OUTPUT_BY_ENDPOINT[endpoint] + if info ~= nil then + local child = device:get_child_by_parent_assigned_key(info.key) + if child and not child:get_field(field_key) then + child:register_native_capability_attr_handler("switch", "switch") + child:set_field(field_key, true) + end + return + end + + if not device:get_field(field_key) then + device:register_native_capability_attr_handler("switch", "switch") + device:set_field(field_key, true) + end +end + +local function on_off_attr_handler(driver, device, value, zb_message) + local endpoint = zb_message.address_header.src_endpoint.value + register_native_switch_handler(device, endpoint) + emit_switch_event_for_endpoint(device, endpoint, value.value and Switch.switch.on() or Switch.switch.off()) +end + +local function build_bind_request(device, src_cluster, src_ep_id, dest_ep_id) + local addr_header = messages.AddressHeader(constants.HUB.ADDR, constants.HUB.ENDPOINT, device:get_short_address(), + device.fingerprinted_endpoint_id, constants.ZDO_PROFILE_ID, bind_request.BindRequest.ID) + + local bind_req = bind_request.BindRequest(device.zigbee_eui, src_ep_id, + src_cluster, + bind_request.ADDRESS_MODE_64_BIT, device.zigbee_eui, dest_ep_id) + local message_body = zdo_messages.ZdoMessageBody({ + zdo_body = bind_req + }) + local bind_cmd = messages.ZigbeeMessageTx({ + address_header = addr_header, + body = message_body + }) + return bind_cmd +end + +local function build_unbind_request(device, src_cluster, src_ep_id, dest_ep_id) + local addr_header = messages.AddressHeader(constants.HUB.ADDR, constants.HUB.ENDPOINT, device:get_short_address(), + device.fingerprinted_endpoint_id, constants.ZDO_PROFILE_ID, unbind_request.UNBIND_REQUEST_CLUSTER_ID) + + local unbind_req = unbind_request.UnbindRequest(device.zigbee_eui, src_ep_id, + src_cluster, + unbind_request.ADDRESS_MODE_64_BIT, device.zigbee_eui, dest_ep_id) + local message_body = zdo_messages.ZdoMessageBody({ + zdo_body = unbind_req + }) + local bind_cmd = messages.ZigbeeMessageTx({ + address_header = addr_header, + body = message_body + }) + return bind_cmd +end + +local function apply_input_preference_changes(device, old_prefs, config) + if old_prefs[config.reverse_pref] ~= device.preferences[config.reverse_pref] then + write_basic_input_polarity_attr(device, config.endpoint, device.preferences[config.reverse_pref]) + end + + for _, bind_cfg in ipairs(config.binds) do + if old_prefs[bind_cfg.pref] ~= device.preferences[bind_cfg.pref] then + device:send(device.preferences[bind_cfg.pref] + and build_bind_request(device, BasicInput.ID, config.endpoint, bind_cfg.endpoint) + or build_unbind_request(device, BasicInput.ID, config.endpoint, bind_cfg.endpoint)) + end + end +end + +local function component_to_endpoint(device, component_id) + if component_id == COMPONENTS.INPUT_1 then + return ZIGBEE_ENDPOINTS.INPUT_1 + elseif component_id == COMPONENTS.INPUT_2 then + return ZIGBEE_ENDPOINTS.INPUT_2 + elseif component_id == COMPONENTS.INPUT_3 then + return ZIGBEE_ENDPOINTS.INPUT_3 + elseif component_id == COMPONENTS.INPUT_4 then + return ZIGBEE_ENDPOINTS.INPUT_4 + elseif component_id == COMPONENTS.OUTPUT_1 then + return ZIGBEE_ENDPOINTS.OUTPUT_1 + elseif component_id == COMPONENTS.OUTPUT_2 then + return ZIGBEE_ENDPOINTS.OUTPUT_2 + else + return device.fingerprinted_endpoint_id + end +end + +local function endpoint_to_component(device, ep) + local ep_id = type(ep) == "table" and ep.value or ep + if ep_id == ZIGBEE_ENDPOINTS.INPUT_1 then + return COMPONENTS.INPUT_1 + elseif ep_id == ZIGBEE_ENDPOINTS.INPUT_2 then + return COMPONENTS.INPUT_2 + elseif ep_id == ZIGBEE_ENDPOINTS.INPUT_3 then + return COMPONENTS.INPUT_3 + elseif ep_id == ZIGBEE_ENDPOINTS.INPUT_4 then + return COMPONENTS.INPUT_4 + elseif ep_id == ZIGBEE_ENDPOINTS.OUTPUT_1 then + return COMPONENTS.OUTPUT_1 + elseif ep_id == ZIGBEE_ENDPOINTS.OUTPUT_2 then + return COMPONENTS.OUTPUT_2 + else + return "main" + end +end + +local function init_handler(self, device) + device:set_component_to_endpoint_fn(component_to_endpoint) + device:set_endpoint_to_component_fn(endpoint_to_component) +end + +local function added_handler(self, device) + ensure_child_devices(self, device) +end + +local function configure_handler(self, device) + local configuration = configurationMap.get_device_configuration(device) + if configuration ~= nil then + for _, attribute in ipairs(configuration) do + if attribute.configurable ~= false then + device:add_configured_attribute(attribute) + end + end + end + device:configure() + if device.parent_assigned_child_key ~= nil then + return + end + + ensure_child_devices(self, device) + + local on1, off1 = get_output_timing(device, "1") + device:send(write_client_manufacturer_specific_attribute(device, BasicInput.ID, + ZIGBEE_MFG_ATTRIBUTES.client.OnWithTimeOff_OnTime.ID, ZIGBEE_MFG_CODES.Develco, + ZIGBEE_MFG_ATTRIBUTES.client.OnWithTimeOff_OnTime.data_type, + on1):to_endpoint(ZIGBEE_ENDPOINTS.OUTPUT_1)) + device:send(write_client_manufacturer_specific_attribute(device, BasicInput.ID, + ZIGBEE_MFG_ATTRIBUTES.client.OnWithTimeOff_OffWaitTime.ID, ZIGBEE_MFG_CODES.Develco, + ZIGBEE_MFG_ATTRIBUTES.client.OnWithTimeOff_OffWaitTime.data_type, + off1):to_endpoint(ZIGBEE_ENDPOINTS.OUTPUT_1)) + + local on2, off2 = get_output_timing(device, "2") + device:send(write_client_manufacturer_specific_attribute(device, BasicInput.ID, + ZIGBEE_MFG_ATTRIBUTES.client.OnWithTimeOff_OnTime.ID, ZIGBEE_MFG_CODES.Develco, + ZIGBEE_MFG_ATTRIBUTES.client.OnWithTimeOff_OnTime.data_type, + on2):to_endpoint(ZIGBEE_ENDPOINTS.OUTPUT_2)) + device:send(write_client_manufacturer_specific_attribute(device, BasicInput.ID, + ZIGBEE_MFG_ATTRIBUTES.client.OnWithTimeOff_OffWaitTime.ID, ZIGBEE_MFG_CODES.Develco, + ZIGBEE_MFG_ATTRIBUTES.client.OnWithTimeOff_OffWaitTime.data_type, + off2):to_endpoint(ZIGBEE_ENDPOINTS.OUTPUT_2)) + + -- Input 1 + local default_old_prefs = {} + for _, config in ipairs(INPUT_CONFIGS) do + apply_input_preference_changes(device, default_old_prefs, config) + end +end + +local function info_changed_handler(self, device, event, args) + if device.parent_assigned_child_key ~= nil then + -- This is a child device + local parent = device:get_parent_device() + if not parent then return end + + local info = OUTPUT_BY_KEY[device.parent_assigned_child_key] + if not info then return end + + -- Child devices have simple preference names without suffix + local on_time = math.floor(sanitize_timing(device.preferences.configOnTime) * 10) + local off_wait = math.floor(sanitize_timing(device.preferences.configOffWaitTime) * 10) + + parent:send(write_client_manufacturer_specific_attribute(parent, BasicInput.ID, + ZIGBEE_MFG_ATTRIBUTES.client.OnWithTimeOff_OnTime.ID, ZIGBEE_MFG_CODES.Develco, + ZIGBEE_MFG_ATTRIBUTES.client.OnWithTimeOff_OnTime.data_type, + on_time):to_endpoint(info.endpoint)) + + parent:send(write_client_manufacturer_specific_attribute(parent, BasicInput.ID, + ZIGBEE_MFG_ATTRIBUTES.client.OnWithTimeOff_OffWaitTime.ID, ZIGBEE_MFG_CODES.Develco, + ZIGBEE_MFG_ATTRIBUTES.client.OnWithTimeOff_OffWaitTime.data_type, + off_wait):to_endpoint(info.endpoint)) + return + else + local old_prefs = (args.old_st_store and args.old_st_store.preferences) or {} + for _, config in ipairs(INPUT_CONFIGS) do + apply_input_preference_changes(device, old_prefs, config) + end + end +end + +local function present_value_attr_handler(driver, device, value, zb_message) + local ep_id = zb_message.address_header.src_endpoint + register_native_switch_handler(device, ep_id.value) + device:emit_event_for_endpoint(ep_id, value.value and Switch.switch.on() or Switch.switch.off()) +end + +local function on_off_default_response_handler(driver, device, zb_rx) + local status = zb_rx.body.zcl_body.status.value + local endpoint = zb_rx.address_header.src_endpoint.value + + if status == Status.SUCCESS then + local cmd = zb_rx.body.zcl_body.cmd.value + local event = nil + + if cmd == OnOff.server.commands.On.ID then + event = Switch.switch.on() + elseif cmd == OnOff.server.commands.OnWithTimedOff.ID then + device:send(cluster_base.read_attribute(device, data_types.ClusterId(OnOff.ID), + data_types.AttributeId(OnOff.attributes.OnOff.ID)):to_endpoint(endpoint)) + elseif cmd == OnOff.server.commands.Off.ID then + event = Switch.switch.off() + end + + if event ~= nil then + emit_switch_event_for_endpoint(device, endpoint, event) + end + end +end + +local function make_switch_handler(command_name) + return function(driver, device, command) + local parent = device:get_parent_device() + if parent then + local info = OUTPUT_BY_KEY[device.parent_assigned_child_key] + if info then + handle_output_command(parent, info.suffix, command_name) + return + end + end + + local num = command.component and command.component:match("output(%d)") + if num then + handle_output_command(device, num, command_name) + return + end + num = command.component:match("input(%d)") + if num then + local component = device.profile.components[command.component] + local value = device:get_latest_state(command.component, Switch.ID, Switch.switch.NAME) + if value == "on" then + device:emit_component_event(component, + Switch.switch.on({ state_change = true, visibility = { displayed = false } })) + elseif value == "off" then + device:emit_component_event(component, + Switch.switch.off({ state_change = true, visibility = { displayed = false } })) + end + end + end +end + +local frient_bridge_handler = { + NAME = "frient bridge handler", + zigbee_handlers = { + global = { + [OnOff.ID] = { + [zcl_global_commands.DEFAULT_RESPONSE_ID] = on_off_default_response_handler + } + }, + cluster = {}, + attr = { + [BasicInput.ID] = { + [BasicInput.attributes.PresentValue.ID] = present_value_attr_handler + }, + [OnOff.ID] = { + [OnOff.attributes.OnOff.ID] = on_off_attr_handler + } + }, + zdo = {} + }, + capability_handlers = { + [Switch.ID] = { + [Switch.commands.on.NAME] = make_switch_handler("on"), + [Switch.commands.off.NAME] = make_switch_handler("off") + } + }, + lifecycle_handlers = { + added = added_handler, + init = init_handler, + doConfigure = configure_handler, + infoChanged = info_changed_handler + }, + can_handle = require("frient-IO.can_handle"), +} + +return frient_bridge_handler diff --git a/drivers/SmartThings/zigbee-switch/src/frient-IO/unbind_request.lua b/drivers/SmartThings/zigbee-switch/src/frient-IO/unbind_request.lua new file mode 100644 index 0000000000..64c4b05464 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/frient-IO/unbind_request.lua @@ -0,0 +1,84 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 +local data_types = require "st.zigbee.data_types" +local utils = require "st.zigbee.utils" + +local unbind_request = {} + +unbind_request.UNBIND_REQUEST_CLUSTER_ID = 0x0022 +unbind_request.ADDRESS_MODE_16_BIT = 0x01 +unbind_request.ADDRESS_MODE_64_BIT = 0x03 + +local UnbindRequest = { + ID = unbind_request.UNBIND_REQUEST_CLUSTER_ID, + NAME = "UnbindRequest", +} +UnbindRequest.__index = UnbindRequest +unbind_request.UnbindRequest = UnbindRequest + +function UnbindRequest.deserialize(buf) + local self = {} + setmetatable(self, UnbindRequest) + + local fields = { + { name = "src_address", type = data_types.IeeeAddress }, + { name = "src_endpoint", type = data_types.Uint8 }, + { name = "cluster_id", type = data_types.ClusterId }, + { name = "dest_addr_mode", type = data_types.Uint8 }, + } + utils.deserialize_field_list(self, fields, buf) + + if self.dest_addr_mode.value == unbind_request.ADDRESS_MODE_16_BIT then + self.dest_address = data_types.Uint16.deserialize(buf) + else + self.dest_address = data_types.IeeeAddress.deserialize(buf) + self.dest_endpoint = data_types.Uint8.deserialize(buf) + end + return self +end + +--- A helper function used by common code to get all the component pieces of this message frame +function UnbindRequest:get_fields() + local out = {} + out[#out + 1] = self.src_address + out[#out + 1] = self.src_endpoint + out[#out + 1] = self.cluster_id + out[#out + 1] = self.dest_addr_mode + out[#out + 1] = self.dest_address + if self.dest_addr_mode.value == unbind_request.ADDRESS_MODE_64_BIT then + out[#out + 1] = self.dest_endpoint + end + return out +end + +UnbindRequest.get_length = utils.length_from_fields +UnbindRequest._serialize = utils.serialize_from_fields +UnbindRequest.pretty_print = utils.print_from_fields +UnbindRequest.__tostring = UnbindRequest.pretty_print +function UnbindRequest.from_values(orig, src_address, src_endpoint, cluster_id, dest_addr_mode, dest_address, + dest_endpoint) + local out = {} + if src_address == nil or src_endpoint == nil or cluster_id == nil or dest_addr_mode == nil or dest_address == nil then + error("Missing necessary values for bind request", 2) + end + + out.src_address = data_types.validate_or_build_type(src_address, data_types.IeeeAddress, "src_address") + out.src_endpoint = data_types.validate_or_build_type(src_endpoint, data_types.Uint8, "src_endpoint") + out.cluster_id = data_types.validate_or_build_type(cluster_id, data_types.ClusterId, "cluster") + out.dest_addr_mode = data_types.validate_or_build_type(dest_addr_mode, data_types.Uint8, "dest_addr_mode") + if (out.dest_addr_mode.value == unbind_request.ADDRESS_MODE_16_BIT) then + out.dest_address = data_types.validate_or_build_type(dest_address, data_types.Uint16, "dest_address") + elseif out.dest_addr_mode.value == unbind_request.ADDRESS_MODE_64_BIT then + out.dest_address = data_types.validate_or_build_type(dest_address, data_types.IeeeAddress, "dest_address") + out.dest_endpoint = data_types.validate_or_build_type(dest_endpoint, data_types.Uint8, "dest_endpoint") + else + error(string.format("Unrecognized destination address mode: %d", out.dest_addr_mode.value), 2) + end + + setmetatable(out, UnbindRequest) + return out +end + +setmetatable(unbind_request.UnbindRequest, { __call = unbind_request.UnbindRequest.from_values }) + +return unbind_request diff --git a/drivers/SmartThings/zigbee-switch/src/frient/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/frient/can_handle.lua new file mode 100644 index 0000000000..2892763034 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/frient/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +-- Function to determine if the driver can handle this device +return function(opts, driver, device, ...) + local FRIENT_SMART_PLUG_FINGERPRINTS = require("frient.fingerprints") + for _, fingerprint in ipairs(FRIENT_SMART_PLUG_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("frient") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/frient/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/frient/fingerprints.lua new file mode 100644 index 0000000000..e2ac5ceaab --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/frient/fingerprints.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "frient A/S", model = "SPLZB-131" }, + { mfr = "frient A/S", model = "SPLZB-132" }, + { mfr = "frient A/S", model = "SPLZB-134" }, + { mfr = "frient A/S", model = "SPLZB-137" }, + { mfr = "frient A/S", model = "SPLZB-141" }, + { mfr = "frient A/S", model = "SPLZB-142" }, + { mfr = "frient A/S", model = "SPLZB-144" }, + { mfr = "frient A/S", model = "SPLZB-147" }, + { mfr = "frient A/S", model = "SMRZB-143" }, + { mfr = "frient A/S", model = "SMRZB-153" }, + { mfr = "frient A/S", model = "SMRZB-332" }, + { mfr = "frient A/S", model = "SMRZB-342" }, +} diff --git a/drivers/SmartThings/zigbee-switch/src/frient/init.lua b/drivers/SmartThings/zigbee-switch/src/frient/init.lua index c075fa9320..a615c721f4 100644 --- a/drivers/SmartThings/zigbee-switch/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/frient/init.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -27,20 +16,6 @@ local VOLTAGE_MEASUREMENT_DIVISOR_KEY = "_voltage_measurement_divisor" local CURRENT_MEASUREMENT_MULTIPLIER_KEY = "_current_measurement_multiplier" local CURRENT_MEASUREMENT_DIVISOR_KEY = "_current_measurement_divisor" -local FRIENT_SMART_PLUG_FINGERPRINTS = { - { mfr = "frient A/S", model = "SPLZB-131" }, - { mfr = "frient A/S", model = "SPLZB-132" }, - { mfr = "frient A/S", model = "SPLZB-134" }, - { mfr = "frient A/S", model = "SPLZB-137" }, - { mfr = "frient A/S", model = "SPLZB-141" }, - { mfr = "frient A/S", model = "SPLZB-142" }, - { mfr = "frient A/S", model = "SPLZB-144" }, - { mfr = "frient A/S", model = "SPLZB-147" }, - { mfr = "frient A/S", model = "SMRZB-143" }, - { mfr = "frient A/S", model = "SMRZB-153" }, - { mfr = "frient A/S", model = "SMRZB-332" }, - { mfr = "frient A/S", model = "SMRZB-342" }, -} local POWER_FAILURE_ALARM_CODE = 0x03 @@ -156,17 +131,6 @@ local function do_configure(driver, device) device:refresh() end --- Function to determine if the driver can handle this device -local function can_handle_frient_smart_plug(opts, driver, device, ...) - for _, fingerprint in ipairs(FRIENT_SMART_PLUG_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("frient") - return true, subdriver - end - end - return false -end - -- Main driver definition local frient_smart_plug = { NAME = "frient Smart Plug", @@ -192,7 +156,7 @@ local frient_smart_plug = { doConfigure = do_configure, added = device_added, }, - can_handle = can_handle_frient_smart_plug + can_handle = require("frient.can_handle"), } -return frient_smart_plug \ No newline at end of file +return frient_smart_plug diff --git a/drivers/SmartThings/zigbee-switch/src/ge-link-bulb/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/ge-link-bulb/can_handle.lua new file mode 100644 index 0000000000..6addbc463c --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/ge-link-bulb/can_handle.lua @@ -0,0 +1,22 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local GE_LINK_BULB_FINGERPRINTS = { + ["GE_Appliances"] = { + ["ZLL Light"] = true, + }, + ["GE"] = { + ["Daylight"] = true, + ["SoftWhite"] = true + } +} + + local can_handle = (GE_LINK_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()] + if can_handle then + local subdriver = require("ge-link-bulb") + return true, subdriver + else + return false + end +end diff --git a/drivers/SmartThings/zigbee-switch/src/ge-link-bulb/init.lua b/drivers/SmartThings/zigbee-switch/src/ge-link-bulb/init.lua index 3bf3fa8952..b1f5ea8508 100644 --- a/drivers/SmartThings/zigbee-switch/src/ge-link-bulb/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/ge-link-bulb/init.lua @@ -1,42 +1,11 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" local Level = clusters.Level -local GE_LINK_BULB_FINGERPRINTS = { - ["GE_Appliances"] = { - ["ZLL Light"] = true, - }, - ["GE"] = { - ["Daylight"] = true, - ["SoftWhite"] = true - } -} - -local function can_handle_ge_link_bulb(opts, driver, device) - local can_handle = (GE_LINK_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()] - if can_handle then - local subdriver = require("ge-link-bulb") - return true, subdriver - else - return false - end -end - local function info_changed(driver, device, event, args) local command local new_dim_onoff_value = tonumber(device.preferences.dimOnOff) @@ -81,7 +50,7 @@ local ge_link_bulb = { [capabilities.switchLevel.commands.setLevel.NAME] = set_level_handler } }, - can_handle = can_handle_ge_link_bulb + can_handle = require("ge-link-bulb.can_handle"), } return ge_link_bulb diff --git a/drivers/SmartThings/zigbee-switch/src/hanssem/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/hanssem/can_handle.lua new file mode 100644 index 0000000000..9f9f1c4ace --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/hanssem/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require "hanssem.fingerprints" + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("hanssem") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/hanssem/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/hanssem/fingerprints.lua new file mode 100644 index 0000000000..65c4421053 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/hanssem/fingerprints.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "Winners", model = "LSS1-101", children = 0 }, + { mfr = "Winners", model = "LSS1-102", children = 1 }, + { mfr = "Winners", model = "LSS1-103", children = 2 }, + { mfr = "Winners", model = "LSS1-204", children = 3 }, + { mfr = "Winners", model = "LSS1-205", children = 4 }, + { mfr = "Winners", model = "LSS1-206", children = 5 } +} diff --git a/drivers/SmartThings/zigbee-switch/src/hanssem/init.lua b/drivers/SmartThings/zigbee-switch/src/hanssem/init.lua index bb845984ca..083893448b 100644 --- a/drivers/SmartThings/zigbee-switch/src/hanssem/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/hanssem/init.lua @@ -1,40 +1,11 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local stDevice = require "st.device" local configurations = require "configurations" -local FINGERPRINTS = { - { mfr = "Winners", model = "LSS1-101", children = 0 }, - { mfr = "Winners", model = "LSS1-102", children = 1 }, - { mfr = "Winners", model = "LSS1-103", children = 2 }, - { mfr = "Winners", model = "LSS1-204", children = 3 }, - { mfr = "Winners", model = "LSS1-205", children = 4 }, - { mfr = "Winners", model = "LSS1-206", children = 5 } -} - -local function can_handle_hanssem_switch(opts, driver, device, ...) - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("hanssem") - return true, subdriver - end - end - return false -end - local function get_children_amount(device) + local FINGERPRINTS = require "hanssem.fingerprints" for _, fingerprint in ipairs(FINGERPRINTS) do if device:get_model() == fingerprint.model then return fingerprint.children @@ -81,7 +52,7 @@ local HanssemSwitch = { added = device_added, init = configurations.power_reconfig_wrapper(device_init) }, - can_handle = can_handle_hanssem_switch + can_handle = require("hanssem.can_handle"), } -return HanssemSwitch \ No newline at end of file +return HanssemSwitch diff --git a/drivers/SmartThings/zigbee-switch/src/ikea-xy-color-bulb/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/ikea-xy-color-bulb/can_handle.lua new file mode 100644 index 0000000000..47992c0559 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/ikea-xy-color-bulb/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +return function(opts, driver, device) + local IKEA_XY_COLOR_BULB_FINGERPRINTS = { + ["IKEA of Sweden"] = { + ["TRADFRI bulb E27 CWS opal 600lm"] = true, + ["TRADFRI bulb E26 CWS opal 600lm"] = true + } + } + local res = (IKEA_XY_COLOR_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()] + if res then + return res, require("ikea-xy-color-bulb") + end + return res +end diff --git a/drivers/SmartThings/zigbee-switch/src/ikea-xy-color-bulb/init.lua b/drivers/SmartThings/zigbee-switch/src/ikea-xy-color-bulb/init.lua index b7080ad446..a026ac6564 100644 --- a/drivers/SmartThings/zigbee-switch/src/ikea-xy-color-bulb/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/ikea-xy-color-bulb/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -27,23 +16,6 @@ local HUESAT_TIMER = "huesat_timer" local TARGET_HUE = "target_hue" local TARGET_SAT = "target_sat" -local IKEA_XY_COLOR_BULB_FINGERPRINTS = { - ["IKEA of Sweden"] = { - ["TRADFRI bulb E27 CWS opal 600lm"] = true, - ["TRADFRI bulb E26 CWS opal 600lm"] = true - } -} - -local function can_handle_ikea_xy_color_bulb(opts, driver, device) - local can_handle = (IKEA_XY_COLOR_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()] - if can_handle then - local subdriver = require("ikea-xy-color-bulb") - return true, subdriver - else - return false - end -end - local device_init = function(self, device) device:remove_configured_attribute(ColorControl.ID, ColorControl.attributes.CurrentHue.ID) device:remove_configured_attribute(ColorControl.ID, ColorControl.attributes.CurrentSaturation.ID) @@ -192,7 +164,7 @@ local ikea_xy_color_bulb = { } } }, - can_handle = can_handle_ikea_xy_color_bulb + can_handle = require("ikea-xy-color-bulb.can_handle") } return ikea_xy_color_bulb diff --git a/drivers/SmartThings/zigbee-switch/src/init.lua b/drivers/SmartThings/zigbee-switch/src/init.lua index 28beb014f4..a7be3f8801 100644 --- a/drivers/SmartThings/zigbee-switch/src/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- The only reason we need this is because of supported_capabilities on the driver template local capabilities = require "st.capabilities" @@ -29,17 +18,6 @@ else lazy_handler = require end -local function lazy_load_if_possible(sub_driver_name) - -- gets the current lua libs api version - -- version 9 will include the lazy loading functions - if version.api >= 9 then - return ZigbeeDriver.lazy_load_sub_driver(require(sub_driver_name)) - else - return require(sub_driver_name) - end - -end - local function component_to_endpoint(device, component_id) local ep_num = component_id:match("switch(%d)") return ep_num and tonumber(ep_num) or device.fingerprinted_endpoint_id @@ -76,6 +54,8 @@ local device_init = function(driver, device) end end +local lazy_load_if_possible = require "lazy_load_subdriver" + local zigbee_switch_driver_template = { supported_capabilities = { capabilities.switch, @@ -84,7 +64,10 @@ local zigbee_switch_driver_template = { capabilities.colorTemperature, capabilities.powerMeter, capabilities.energyMeter, - capabilities.motionSensor + capabilities.motionSensor, + capabilities.illuminanceMeasurement, + capabilities.relativeHumidityMeasurement, + capabilities.temperatureMeasurement, }, sub_drivers = { lazy_load_if_possible("non_zigbee_devices"), @@ -112,10 +95,11 @@ local zigbee_switch_driver_template = { lazy_load_if_possible("bad_on_off_data_type"), lazy_load_if_possible("robb"), lazy_load_if_possible("wallhero"), - lazy_load_if_possible("inovelli-vzm31-sn"), + lazy_load_if_possible("inovelli"), -- Combined driver for both VZM31-SN and VZM32-SN lazy_load_if_possible("laisiao"), lazy_load_if_possible("tuya-multi"), - lazy_load_if_possible("frient") + lazy_load_if_possible("frient"), + lazy_load_if_possible("frient-IO") }, zigbee_handlers = { global = { diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm31-sn/init.lua b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm31-sn/init.lua deleted file mode 100644 index a0dae6db3f..0000000000 --- a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm31-sn/init.lua +++ /dev/null @@ -1,400 +0,0 @@ --- 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 clusters = require "st.zigbee.zcl.clusters" -local cluster_base = require "st.zigbee.cluster_base" -local utils = require "st.utils" -local st_device = require "st.device" -local data_types = require "st.zigbee.data_types" -local capabilities = require "st.capabilities" -local device_management = require "st.zigbee.device_management" -local configurations = require "configurations" -local switch_utils = require "switch_utils" - -local LATEST_CLOCK_SET_TIMESTAMP = "latest_clock_set_timestamp" - -local INOVELLI_VZM31_SN_FINGERPRINTS = { - { mfr = "Inovelli", model = "VZM31-SN" } -} - -local PRIVATE_CLUSTER_ID = 0xFC31 -local PRIVATE_CMD_NOTIF_ID = 0x01 -local PRIVATE_CMD_SCENE_ID =0x00 -local MFG_CODE = 0x122F - -local preference_map = { - parameter258 = {parameter_number = 258, size = data_types.Boolean}, - parameter22 = {parameter_number = 22, size = data_types.Uint8}, - parameter52 = {parameter_number = 52, size = data_types.Boolean}, - parameter1 = {parameter_number = 1, size = data_types.Uint8}, - parameter2 = {parameter_number = 2, size = data_types.Uint8}, - parameter3 = {parameter_number = 3, size = data_types.Uint8}, - parameter4 = {parameter_number = 4, size = data_types.Uint8}, - parameter9 = {parameter_number = 9, size = data_types.Uint8}, - parameter10 = {parameter_number = 10, size = data_types.Uint8}, - parameter11 = {parameter_number = 11, size = data_types.Boolean}, - parameter15 = {parameter_number = 15, size = data_types.Uint8}, - parameter17 = {parameter_number = 17, size = data_types.Uint8}, - parameter95 = {parameter_number = 95, size = data_types.Uint8}, - parameter96 = {parameter_number = 96, size = data_types.Uint8}, - parameter97 = {parameter_number = 97, size = data_types.Uint8}, - parameter98 = {parameter_number = 98, size = data_types.Uint8}, -} - -local preferences_to_numeric_value = function(new_value) - local numeric = tonumber(new_value) - if numeric == nil then -- in case the value is Boolean - numeric = new_value and 1 or 0 - end - return numeric -end - -local preferences_calculate_parameter = function(new_value, type, number) - if number == "parameter9" or number == "parameter10" or number == "parameter13" or number == "parameter14" or number == "parameter15" or number == "parameter55" or number == "parameter56" then - if new_value == 101 then - return 255 - else - return utils.round(new_value / 100 * 254) - end - else - return new_value - end -end - -local is_inovelli_vzm31_sn = function(opts, driver, device) - for _, fingerprint in ipairs(INOVELLI_VZM31_SN_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("inovelli-vzm31-sn") - return true, subdriver - end - end - return false -end - -local function to_boolean(value) - if value == 0 or value =="0" then - return false - else - return true - end -end - -local map_key_attribute_to_capability = { - [0x00] = capabilities.button.button.pushed, - [0x01] = capabilities.button.button.held, - [0x02] = capabilities.button.button.down_hold, - [0x03] = capabilities.button.button.pushed_2x, - [0x04] = capabilities.button.button.pushed_3x, - [0x05] = capabilities.button.button.pushed_4x, - [0x06] = capabilities.button.button.pushed_5x, -} - -local function button_to_component(buttonId) - if buttonId > 0 then - return string.format("button%d", buttonId) - end -end - -local function scene_handler(driver, device, zb_rx) - local bytes = zb_rx.body.zcl_body.body_bytes - local button_number = bytes:byte(1) - local capability_attribute = map_key_attribute_to_capability[bytes:byte(2)] - local additional_fields = { - state_change = true - } - - local event - if capability_attribute ~= nil then - event = capability_attribute(additional_fields) - end - - local comp = device.profile.components[button_to_component(button_number)] - if comp ~= nil then - device:emit_component_event(comp, event) - end -end - -local function add_child(driver,parent,profile,child_type) - local child_metadata = { - type = "EDGE_CHILD", - label = string.format("%s %s", parent.label, child_type:gsub("(%l)(%w*)", function(a,b) return string.upper(a)..b end)), - profile = profile, - parent_device_id = parent.id, - parent_assigned_child_key = child_type, - vendor_provided_label = string.format("%s %s", parent.label, child_type:gsub("(%l)(%w*)", function(a,b) return string.upper(a)..b end)) - } - driver:try_create_device(child_metadata) -end - -local function info_changed(driver, device, event, args) - if device.network_type ~= st_device.NETWORK_TYPE_CHILD then - local time_diff = 3 - local last_clock_set_time = device:get_field(LATEST_CLOCK_SET_TIMESTAMP) - if last_clock_set_time ~= nil then - time_diff = os.difftime(os.time(), last_clock_set_time) - end - device:set_field(LATEST_CLOCK_SET_TIMESTAMP, os.time(), {persist = true}) - - if time_diff > 2 then - local preferences = preference_map - if args.old_st_store.preferences["notificationChild"] ~= device.preferences.notificationChild and args.old_st_store.preferences["notificationChild"] == false and device.preferences.notificationChild == true then - if not device:get_child_by_parent_assigned_key('notification') then - add_child(driver,device,'rgbw-bulb-2700K-6500K','notification') - end - end - for id, value in pairs(device.preferences) do - if args.old_st_store.preferences[id] ~= value and preferences and preferences[id] then - local new_parameter_value = preferences_calculate_parameter(preferences_to_numeric_value(device.preferences[id]), preferences[id].size, id) - - if(preferences[id].size == data_types.Boolean) then - device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, preferences[id].parameter_number, MFG_CODE, preferences[id].size, to_boolean(new_parameter_value))) - else - device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, preferences[id].parameter_number, MFG_CODE, preferences[id].size, new_parameter_value)) - end - end - end - device:send(cluster_base.read_attribute(device, data_types.ClusterId(0x0000), 0x4000)) - end - end -end - -local do_configure = function(self, device) - if device.network_type ~= st_device.NETWORK_TYPE_CHILD then - device:refresh() - device:configure() - - device:send(device_management.build_bind_request(device, PRIVATE_CLUSTER_ID, self.environment_info.hub_zigbee_eui, 2)) -- Bind device for button presses. - - -- Retrieve Neutral Setting "Parameter 21" - device:send(cluster_base.read_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, 21, MFG_CODE)) - device:send(cluster_base.read_attribute(device, data_types.ClusterId(0x0000), 0x4000)) - - -- Additional one time configuration - if device:supports_capability(capabilities.powerMeter) then - -- Divisor and multipler for PowerMeter - device:send(clusters.SimpleMetering.attributes.Divisor:read(device)) - device:send(clusters.SimpleMetering.attributes.Multiplier:read(device)) - end - - if device:supports_capability(capabilities.energyMeter) then - -- Divisor and multipler for EnergyMeter - device:send(clusters.ElectricalMeasurement.attributes.ACPowerDivisor:read(device)) - device:send(clusters.ElectricalMeasurement.attributes.ACPowerMultiplier:read(device)) - end - end -end - -local device_init = function(self, device) - if device.network_type ~= st_device.NETWORK_TYPE_CHILD then - device:set_field(LATEST_CLOCK_SET_TIMESTAMP, os.time()) - if device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) == nil and device:supports_capability(capabilities.switchLevel)then - device:emit_event(capabilities.switchLevel.level(0)) - end - if device:get_latest_state("main", capabilities.powerMeter.ID, capabilities.powerMeter.power.NAME) == nil and device:supports_capability(capabilities.powerMeter) then - device:emit_event(capabilities.powerMeter.power(0)) - end - if device:get_latest_state("main", capabilities.energyMeter.ID, capabilities.energyMeter.energy.NAME) == nil and device:supports_capability(capabilities.energyMeter)then - device:emit_event(capabilities.energyMeter.energy(0)) - end - - for _, component in pairs(device.profile.components) do - if string.find(component.id, "button") ~= nil then - if device:get_latest_state(component.id, capabilities.button.ID, capabilities.button.supportedButtonValues.NAME) == nil then - device:emit_component_event( - component, - capabilities.button.supportedButtonValues( - {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, - { visibility = { displayed = false } } - ) - ) - end - if device:get_latest_state(component.id, capabilities.button.ID, capabilities.button.numberOfButtons.NAME) == nil then - device:emit_component_event( - component, - capabilities.button.numberOfButtons({value = 1}, { visibility = { displayed = false } }) - ) - end - end - end - device:send(cluster_base.read_attribute(device, data_types.ClusterId(0x0000), 0x4000)) - else - switch_utils.emit_event_if_latest_state_missing(device, "main", capabilities.colorControl, capabilities.colorControl.hue.NAME, capabilities.colorControl.hue(1)) - switch_utils.emit_event_if_latest_state_missing(device, "main", capabilities.colorControl, capabilities.colorControl.saturation.NAME, capabilities.colorControl.saturation(1)) - switch_utils.emit_event_if_latest_state_missing(device, "main", capabilities.colorTemperature, capabilities.colorTemperature.colorTemperature.NAME, capabilities.colorTemperature.colorTemperature(6500)) - switch_utils.emit_event_if_latest_state_missing(device, "main", capabilities.switchLevel, capabilities.switchLevel.level.NAME, capabilities.switchLevel.level(100)) - switch_utils.emit_event_if_latest_state_missing(device, "main", capabilities.switch, capabilities.switch.switch.NAME, capabilities.switch.switch("off")) - end -end - -local function energy_meter_handler(driver, device, value, zb_rx) - local raw_value = value.value - raw_value = raw_value / 100 - device:emit_event(capabilities.energyMeter.energy({value = raw_value, unit = "kWh" })) -end - -local function power_meter_handler(driver, device, value, zb_rx) - local raw_value = value.value - raw_value = raw_value / 10 - device:emit_event(capabilities.powerMeter.power({value = raw_value, unit = "W" })) -end - -local function huePercentToValue(value) - if value <= 2 then - return 0 - elseif value >= 98 then - return 255 - else - return utils.round(value / 100 * 255) - end -end - -local function getNotificationValue(device, value) - local notificationValue = 0 - local level = device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) or 100 - local color = utils.round(device:get_latest_state("main", capabilities.colorControl.ID, capabilities.colorControl.hue.NAME) or 100) - local effect = device:get_parent_device().preferences.notificationType or 1 - notificationValue = notificationValue + (effect*16777216) - notificationValue = notificationValue + (huePercentToValue(value or color)*65536) - notificationValue = notificationValue + (level*256) - notificationValue = notificationValue + (255*1) - return notificationValue -end - -local function on_handler(driver, device, command) - if device.network_type ~= st_device.NETWORK_TYPE_CHILD then - device:send(clusters.OnOff.server.commands.On(device)) - else - device:emit_event(capabilities.switch.switch("on")) - local dev = device:get_parent_device() - local send_configuration = function() - dev:send(cluster_base.build_manufacturer_specific_command( - dev, - PRIVATE_CLUSTER_ID, - PRIVATE_CMD_NOTIF_ID, - MFG_CODE, - utils.serialize_int(getNotificationValue(device),4,false,false))) - end - device.thread:call_with_delay(1,send_configuration) - end -end - -local function off_handler(driver, device, command) - if device.network_type ~= st_device.NETWORK_TYPE_CHILD then - device:send(clusters.OnOff.server.commands.Off(device)) - else - device:emit_event(capabilities.switch.switch("off")) - local dev = device:get_parent_device() - local send_configuration = function() - dev:send(cluster_base.build_manufacturer_specific_command( - dev, - PRIVATE_CLUSTER_ID, - PRIVATE_CMD_NOTIF_ID, - MFG_CODE, - utils.serialize_int(0,4,false,false))) - end - device.thread:call_with_delay(1,send_configuration) - end -end - -local function switch_level_handler(driver, device, command) - if device.network_type ~= st_device.NETWORK_TYPE_CHILD then - device:send(clusters.Level.server.commands.MoveToLevelWithOnOff(device, math.floor(command.args.level/100.0 * 254), command.args.rate or 0xFFFF)) - else - device:emit_event(capabilities.switchLevel.level(command.args.level)) - device:emit_event(capabilities.switch.switch(command.args.level ~= 0 and "on" or "off")) - local dev = device:get_parent_device() - local send_configuration = function() - dev:send(cluster_base.build_manufacturer_specific_command( - dev, - PRIVATE_CLUSTER_ID, - PRIVATE_CMD_NOTIF_ID, - MFG_CODE, - utils.serialize_int(getNotificationValue(device),4,false,false))) - end - device.thread:call_with_delay(1,send_configuration) - end -end - -local function set_color_temperature(driver, device, command) - device:emit_event(capabilities.colorControl.hue(100)) - device:emit_event(capabilities.colorTemperature.colorTemperature(command.args.temperature)) - local dev = device:get_parent_device() - local send_configuration = function() - dev:send(cluster_base.build_manufacturer_specific_command( - dev, - PRIVATE_CLUSTER_ID, - PRIVATE_CMD_NOTIF_ID, - MFG_CODE, - utils.serialize_int(getNotificationValue(device, 100),4,false,false))) - end - device.thread:call_with_delay(1,send_configuration) -end - -local function set_color(driver, device, command) - device:emit_event(capabilities.colorControl.hue(command.args.color.hue)) - device:emit_event(capabilities.colorControl.saturation(command.args.color.saturation)) - local dev = device:get_parent_device() - local send_configuration = function() - dev:send(cluster_base.build_manufacturer_specific_command( - dev, - PRIVATE_CLUSTER_ID, - PRIVATE_CMD_NOTIF_ID, - MFG_CODE, - utils.serialize_int(getNotificationValue(device),4,false,false))) - end - device.thread:call_with_delay(1,send_configuration) -end - -local inovelli_vzm31_sn = { - NAME = "inovelli vzm31-sn handler", - lifecycle_handlers = { - doConfigure = do_configure, - init = configurations.power_reconfig_wrapper(device_init), - infoChanged = info_changed - }, - zigbee_handlers = { - attr = { - [clusters.SimpleMetering.ID] = { - [clusters.SimpleMetering.attributes.InstantaneousDemand.ID] = power_meter_handler, - [clusters.SimpleMetering.attributes.CurrentSummationDelivered.ID] = energy_meter_handler - }, - [clusters.ElectricalMeasurement.ID] = { - [clusters.ElectricalMeasurement.attributes.ActivePower.ID] = power_meter_handler - } - }, - cluster = { - [PRIVATE_CLUSTER_ID] = { - [PRIVATE_CMD_SCENE_ID] = scene_handler, - } - } - }, - capability_handlers = { - [capabilities.switch.ID] = { - [capabilities.switch.commands.on.NAME] = on_handler, - [capabilities.switch.commands.off.NAME] = off_handler, - }, - [capabilities.switchLevel.ID] = { - [capabilities.switchLevel.commands.setLevel.NAME] = switch_level_handler - }, - [capabilities.colorControl.ID] = { - [capabilities.colorControl.commands.setColor.NAME] = set_color - }, - [capabilities.colorTemperature.ID] = { - [capabilities.colorTemperature.commands.setColorTemperature.NAME] = set_color_temperature - } - }, - can_handle = is_inovelli_vzm31_sn -} - -return inovelli_vzm31_sn diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/inovelli/can_handle.lua new file mode 100644 index 0000000000..d9b676e307 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/inovelli/can_handle.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +return function(opts, driver, device) + local INOVELLI_FINGERPRINTS = { + { mfr = "Inovelli", model = "VZM30-SN" }, + { mfr = "Inovelli", model = "VZM31-SN" }, + { mfr = "Inovelli", model = "VZM32-SN" } + } + for _, fp in ipairs(INOVELLI_FINGERPRINTS) do + if device:get_manufacturer() == fp.mfr and device:get_model() == fp.model then + local subdriver = require("inovelli") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli/common.lua b/drivers/SmartThings/zigbee-switch/src/inovelli/common.lua new file mode 100644 index 0000000000..c402731328 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/inovelli/common.lua @@ -0,0 +1,73 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local clusters = require "st.zigbee.zcl.clusters" +local device_management = require "st.zigbee.device_management" +local OTAUpgrade = require("st.zigbee.zcl.clusters").OTAUpgrade +local zigbee_constants = require "st.zigbee.constants" +local capabilities = require "st.capabilities" + +local M = {} + +M.supported_button_values = { + ["button1"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button2"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button3"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"} +} + +-- Utility function to check if device is VZM32-SN +function M.is_vzm32(device) + return device:get_model() == "VZM32-SN" +end + +-- Utility function to check if device is VZM32-SN +function M.is_vzm30(device) + return device:get_model() == "VZM30-SN" +end + +-- Sends a generic configure for Inovelli devices (all models): +-- - device:configure +-- - send OTA ImageNotify +-- - bind PRIVATE cluster for button presses +-- - read metering/electrical measurement divisors/multipliers +function M.base_device_configure(driver, device, private_cluster_id, mfg_code) + device:configure() + -- OTA Image Notify (generic for all devices) + local PAYLOAD_TYPE = 0x00 + local QUERY_JITTER = 100 + local IMAGE_TYPE = 0xFFFF + local NEW_VERSION = 0xFFFFFFFF + device:send(OTAUpgrade.commands.ImageNotify(device, PAYLOAD_TYPE, QUERY_JITTER, mfg_code, IMAGE_TYPE, NEW_VERSION)) + + -- Bind for button presses on manufacturer private cluster + device:send(device_management.build_bind_request(device, private_cluster_id, driver.environment_info.hub_zigbee_eui, 2)) + + -- Read divisors/multipliers for power/energy reporting + -- Set default divisor to 1000 for VZM32-SN and VZM30-SN. In initial firmware the divisor is incorrectly set to 100. + if M.is_vzm32(device) or M.is_vzm30(device) then + device:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, 1000, {persist = true}) + else + device:send(clusters.SimpleMetering.attributes.Divisor:read(device)) + end + device:send(clusters.SimpleMetering.attributes.Multiplier:read(device)) + device:send(clusters.ElectricalMeasurement.attributes.ACPowerDivisor:read(device)) + device:send(clusters.ElectricalMeasurement.attributes.ACPowerMultiplier:read(device)) + + for _, component in pairs(device.profile.components) do + if component.id ~= "main" then + device:emit_component_event( + component, + capabilities.button.supportedButtonValues( + M.supported_button_values[component.id], + { visibility = { displayed = false } } + ) + ) + device:emit_component_event( + component, + capabilities.button.numberOfButtons({value = 1}, { visibility = { displayed = false } }) + ) + end + end +end + +return M \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli/init.lua b/drivers/SmartThings/zigbee-switch/src/inovelli/init.lua new file mode 100644 index 0000000000..a83a343663 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/inovelli/init.lua @@ -0,0 +1,411 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local utils = require "st.utils" +local st_device = require "st.device" +local data_types = require "st.zigbee.data_types" +local capabilities = require "st.capabilities" +local inovelli_common = require "inovelli.common" + +-- Load VZM32-only dependencies (handlers will check device type) +local OccupancySensing = clusters.OccupancySensing + +local LATEST_CLOCK_SET_TIMESTAMP = "latest_clock_set_timestamp" + +local PRIVATE_CLUSTER_ID = 0xFC31 +local PRIVATE_CLUSTER_MMWAVE_ID = 0xFC32 +local PRIVATE_CMD_NOTIF_ID = 0x01 +local PRIVATE_CMD_ENERGY_RESET_ID = 0x02 +local PRIVATE_CMD_SCENE_ID = 0x00 +local PRIVATE_CMD_MMWAVE_ID = 0x00 +local MFG_CODE = 0x122F + +-- Base preferences shared by all models +local base_preference_map = { + parameter258 = {parameter_number = 258, size = data_types.Boolean, cluster = PRIVATE_CLUSTER_ID}, + parameter52 = {parameter_number = 52, size = data_types.Boolean, cluster = PRIVATE_CLUSTER_ID}, + parameter1 = {parameter_number = 1, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter2 = {parameter_number = 2, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter3 = {parameter_number = 3, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter4 = {parameter_number = 4, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter15 = {parameter_number = 15, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter95 = {parameter_number = 95, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter96 = {parameter_number = 96, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter97 = {parameter_number = 97, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter98 = {parameter_number = 98, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, +} + +-- Model-specific overrides/additions +local model_preference_overrides = { + ["VZM30-SN"] = { + parameter11 = {parameter_number = 11, size = data_types.Boolean, cluster = PRIVATE_CLUSTER_ID}, + parameter22 = {parameter_number = 22, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + }, + ["VZM31-SN"] = { + parameter9 = {parameter_number = 9, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter10 = {parameter_number = 10, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter11 = {parameter_number = 11, size = data_types.Boolean, cluster = PRIVATE_CLUSTER_ID}, + parameter17 = {parameter_number = 17, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter22 = {parameter_number = 22, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + }, + ["VZM32-SN"] = { + parameter9 = {parameter_number = 9, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter10 = {parameter_number = 10, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter34 = {parameter_number = 34, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter101 = {parameter_number = 101, size = data_types.Int16, cluster = PRIVATE_CLUSTER_MMWAVE_ID}, + parameter102 = {parameter_number = 102, size = data_types.Int16, cluster = PRIVATE_CLUSTER_MMWAVE_ID}, + parameter103 = {parameter_number = 103, size = data_types.Int16, cluster = PRIVATE_CLUSTER_MMWAVE_ID}, + parameter104 = {parameter_number = 104, size = data_types.Int16, cluster = PRIVATE_CLUSTER_MMWAVE_ID}, + parameter105 = {parameter_number = 105, size = data_types.Int16, cluster = PRIVATE_CLUSTER_MMWAVE_ID}, + parameter106 = {parameter_number = 106, size = data_types.Int16, cluster = PRIVATE_CLUSTER_MMWAVE_ID}, + parameter110 = {parameter_number = 110, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_ID}, + parameter111 = {parameter_number = 111, size = data_types.Uint32, cluster = PRIVATE_CLUSTER_MMWAVE_ID}, + parameter112 = {parameter_number = 112, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_MMWAVE_ID}, + parameter113 = {parameter_number = 113, size = data_types.Uint8, cluster = PRIVATE_CLUSTER_MMWAVE_ID}, + parameter114 = {parameter_number = 114, size = data_types.Uint32, cluster = PRIVATE_CLUSTER_MMWAVE_ID}, + parameter115 = {parameter_number = 115, size = data_types.Uint32, cluster = PRIVATE_CLUSTER_ID}, + } +} + +local function get_preference_map_for_device(device) + -- shallow copy base + local merged = {} + for k, v in pairs(base_preference_map) do merged[k] = v end + -- merge model-specific + local model = device and device:get_model() or nil + local override = model and model_preference_overrides[model] or nil + if override then + for k, v in pairs(override) do merged[k] = v end + end + return merged +end + +local preferences_to_numeric_value = function(new_value) + local numeric = tonumber(new_value) + if numeric == nil then + numeric = new_value and 1 or 0 + end + return numeric +end + +local preferences_calculate_parameter = function(new_value, type, number) + if number == "parameter9" or number == "parameter10" or number == "parameter13" or number == "parameter14" or number == "parameter15" or number == "parameter55" or number == "parameter56" then + if new_value == 101 then + return 255 + else + return utils.round(new_value / 100 * 254) + end + else + return new_value + end +end + +local function to_boolean(value) + if value == 0 or value == "0" then + return false + else + return true + end +end + +local map_key_attribute_to_capability = { + [0x00] = capabilities.button.button.pushed, + [0x01] = capabilities.button.button.held, + [0x02] = capabilities.button.button.down_hold, + [0x03] = capabilities.button.button.pushed_2x, + [0x04] = capabilities.button.button.pushed_3x, + [0x05] = capabilities.button.button.pushed_4x, + [0x06] = capabilities.button.button.pushed_5x, +} + +local function button_to_component(buttonId) + if buttonId > 0 then + return string.format("button%d", buttonId) + end +end + +local function scene_handler(driver, device, zb_rx) + local bytes = zb_rx.body.zcl_body.body_bytes + local button_number = bytes:byte(1) + local capability_attribute = map_key_attribute_to_capability[bytes:byte(2)] + local additional_fields = { state_change = true } + local event = capability_attribute and capability_attribute(additional_fields) or nil + local comp_name = button_to_component(button_number) + local comp = device.profile.components[comp_name] + if comp ~= nil and event ~= nil then + -- Check if the event is in the supportedButtonValues before emitting + -- This ensures backward compatibility with devices installed with previous driver versions + local expected_values = inovelli_common.supported_button_values[comp_name] + local supportedEvents = device:get_latest_state( + comp_name, + capabilities.button.ID, + capabilities.button.supportedButtonValues.NAME, + {capabilities.button.button.pushed.NAME} -- default fallback for older devices + ) + + -- Check if supportedButtonValues needs to be updated + -- This handles devices installed with previous driver versions that don't have + -- the updated supportedButtonValues attribute. If the current value only contains + -- "pushed" (the fallback), update it to the full list. + local needs_update = false + if expected_values then + -- Check if current supportedEvents is exactly the fallback (only "pushed") + -- This indicates the state was never set and we're using the fallback value + if #supportedEvents == 1 and supportedEvents[1] == capabilities.button.button.pushed.NAME then + needs_update = true + end + + if needs_update then + device:emit_component_event( + comp, + capabilities.button.supportedButtonValues( + expected_values, + { visibility = { displayed = false } } + ) + ) + supportedEvents = expected_values -- Update local reference for event check + end + end + + -- Check if the event is supported + local event_supported = false + for _, event_name in pairs(supportedEvents) do + if event.value.value == event_name then + event_supported = true + break + end + end + + if event_supported then + device:emit_component_event(comp, event) + end + end +end + +local function add_child(driver,parent,profile,child_type) + local child_metadata = { + type = "EDGE_CHILD", + label = string.format("%s %s", parent.label, child_type:gsub("(%l)(%w*)", function(a,b) return string.upper(a)..b end)), + profile = profile, + parent_device_id = parent.id, + parent_assigned_child_key = child_type, + vendor_provided_label = string.format("%s %s", parent.label, child_type:gsub("(%l)(%w*)", function(a,b) return string.upper(a)..b end)) + } + driver:try_create_device(child_metadata) +end + +local function info_changed(driver, device, event, args) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + local time_diff = 3 + local last_clock_set_time = device:get_field(LATEST_CLOCK_SET_TIMESTAMP) + if last_clock_set_time ~= nil then time_diff = os.difftime(os.time(), last_clock_set_time) end + device:set_field(LATEST_CLOCK_SET_TIMESTAMP, os.time(), {persist = true}) + if time_diff > 2 then + local preferences = get_preference_map_for_device(device) + if args.old_st_store.preferences["notificationChild"] ~= device.preferences.notificationChild and args.old_st_store.preferences["notificationChild"] == false and device.preferences.notificationChild == true then + if not device:get_child_by_parent_assigned_key('notification') then + add_child(driver,device,'rgbw-bulb-2700K-6500K','notification') + end + end + for id, value in pairs(device.preferences) do + if args.old_st_store.preferences[id] ~= value and preferences and preferences[id] then + local new_parameter_value = preferences_calculate_parameter(preferences_to_numeric_value(device.preferences[id]), preferences[id].size, id) + if(preferences[id].size == data_types.Boolean) then + new_parameter_value = to_boolean(new_parameter_value) + end + if id == "parameter111" then + device:send(cluster_base.build_manufacturer_specific_command( + device, + PRIVATE_CLUSTER_MMWAVE_ID, + PRIVATE_CMD_MMWAVE_ID, + MFG_CODE, + utils.serialize_int(new_parameter_value,1,false,false))) + else + device:send(cluster_base.write_manufacturer_specific_attribute(device, preferences[id].cluster, preferences[id].parameter_number, MFG_CODE, preferences[id].size, new_parameter_value)) + end + end + end + end + end +end + +local function device_added(driver, device) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:refresh() + else + device:emit_event(capabilities.colorControl.hue(1)) + device:emit_event(capabilities.colorControl.saturation(1)) + device:emit_event(capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = 2700, maximum = 6500} })) + device:emit_event(capabilities.colorTemperature.colorTemperature(6500)) + device:emit_event(capabilities.switchLevel.level(100)) + device:emit_event(capabilities.switch.switch("off")) + end +end + +local function device_configure(driver, device) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + inovelli_common.base_device_configure(driver, device, PRIVATE_CLUSTER_ID, MFG_CODE) + else + device:configure() + end +end + +local function huePercentToValue(value) + if value <= 2 then return 0 + elseif value >= 98 then return 255 + else return utils.round(value / 100 * 255) end +end + +local function getNotificationValue(device, value) + local notificationValue = 0 + local level = device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) or 100 + local color = utils.round(device:get_latest_state("main", capabilities.colorControl.ID, capabilities.colorControl.hue.NAME) or 100) + local effect = device:get_parent_device().preferences.notificationType or 1 + notificationValue = notificationValue + (effect*16777216) + notificationValue = notificationValue + (huePercentToValue(value or color)*65536) + notificationValue = notificationValue + (level*256) + notificationValue = notificationValue + (255*1) + return notificationValue +end + +local function on_handler(driver, device, command) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:send(clusters.OnOff.server.commands.On(device)) + else + device:emit_event(capabilities.switch.switch("on")) + local dev = device:get_parent_device() + local send_configuration = function() + dev:send(cluster_base.build_manufacturer_specific_command( + dev, + PRIVATE_CLUSTER_ID, + PRIVATE_CMD_NOTIF_ID, + MFG_CODE, + utils.serialize_int(getNotificationValue(device),4,false,false))) + end + device.thread:call_with_delay(1,send_configuration) + end + end + + local function off_handler(driver, device, command) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:send(clusters.OnOff.server.commands.Off(device)) + else + device:emit_event(capabilities.switch.switch("off")) + local dev = device:get_parent_device() + local send_configuration = function() + dev:send(cluster_base.build_manufacturer_specific_command( + dev, + PRIVATE_CLUSTER_ID, + PRIVATE_CMD_NOTIF_ID, + MFG_CODE, + utils.serialize_int(0,4,false,false))) + end + device.thread:call_with_delay(1,send_configuration) + end + end + +local function switch_level_handler(driver, device, command) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:send(clusters.Level.server.commands.MoveToLevelWithOnOff(device, math.floor(command.args.level/100.0 * 254), command.args.rate or 0xFFFF)) + else + device:emit_event(capabilities.switchLevel.level(command.args.level)) + device:emit_event(capabilities.switch.switch(command.args.level ~= 0 and "on" or "off")) + local dev = device:get_parent_device() + local send_configuration = function() + dev:send(cluster_base.build_manufacturer_specific_command( + dev, + PRIVATE_CLUSTER_ID, + PRIVATE_CMD_NOTIF_ID, + MFG_CODE, + utils.serialize_int(getNotificationValue(device),4,false,false))) + end + device.thread:call_with_delay(1,send_configuration) + end + end + +local function set_color_temperature(driver, device, command) + device:emit_event(capabilities.colorControl.hue(100)) + device:emit_event(capabilities.colorTemperature.colorTemperature(command.args.temperature)) + device:emit_event(capabilities.switch.switch("on")) + local dev = device:get_parent_device() + local send_configuration = function() + dev:send(cluster_base.build_manufacturer_specific_command( + dev, + PRIVATE_CLUSTER_ID, + PRIVATE_CMD_NOTIF_ID, + MFG_CODE, + utils.serialize_int(getNotificationValue(device, 100),4,false,false))) + end + device.thread:call_with_delay(1,send_configuration) + end + + local function set_color(driver, device, command) + device:emit_event(capabilities.colorControl.hue(command.args.color.hue)) + device:emit_event(capabilities.colorControl.saturation(command.args.color.saturation)) + device:emit_event(capabilities.switch.switch("on")) + local dev = device:get_parent_device() + local send_configuration = function() + dev:send(cluster_base.build_manufacturer_specific_command( + dev, + PRIVATE_CLUSTER_ID, + PRIVATE_CMD_NOTIF_ID, + MFG_CODE, + utils.serialize_int(getNotificationValue(device),4,false,false))) + end + device.thread:call_with_delay(1,send_configuration) + end + +local function occupancy_attr_handler(driver, device, occupancy, zb_rx) + device:emit_event(occupancy.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) +end + +local function handle_resetEnergyMeter(self, device) + device:send(cluster_base.build_manufacturer_specific_command(device, PRIVATE_CLUSTER_ID, PRIVATE_CMD_ENERGY_RESET_ID, MFG_CODE, utils.serialize_int(0,1,false,false))) + device:send(clusters.SimpleMetering.attributes.CurrentSummationDelivered:read(device)) + device:send(clusters.ElectricalMeasurement.attributes.ActivePower:read(device)) +end + +local inovelli = { + NAME = "Inovelli Zigbee Switch", + lifecycle_handlers = { + doConfigure = device_configure, + infoChanged = info_changed, + added = device_added, + }, + zigbee_handlers = { + attr = { + [OccupancySensing.ID] = { + [OccupancySensing.attributes.Occupancy.ID] = occupancy_attr_handler + }, + }, + cluster = { + [PRIVATE_CLUSTER_ID] = { + [PRIVATE_CMD_SCENE_ID] = scene_handler, + } + } + }, + sub_drivers = require("inovelli.sub_drivers"), + capability_handlers = { + [capabilities.switch.ID] = { + [capabilities.switch.commands.on.NAME] = on_handler, + [capabilities.switch.commands.off.NAME] = off_handler, + }, + [capabilities.switchLevel.ID] = { + [capabilities.switchLevel.commands.setLevel.NAME] = switch_level_handler + }, + [capabilities.colorControl.ID] = { + [capabilities.colorControl.commands.setColor.NAME] = set_color + }, + [capabilities.colorTemperature.ID] = { + [capabilities.colorTemperature.commands.setColorTemperature.NAME] = set_color_temperature + }, + [capabilities.energyMeter.ID] = { + [capabilities.energyMeter.commands.resetEnergyMeter.NAME] = handle_resetEnergyMeter, + } + }, + can_handle = require("inovelli.can_handle"), +} + +return inovelli diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/inovelli/sub_drivers.lua new file mode 100644 index 0000000000..2fc65221be --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/inovelli/sub_drivers.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load = require "lazy_load_subdriver" + +return { + lazy_load("inovelli.vzm30-sn"), + lazy_load("inovelli.vzm32-sn") +} diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli/vzm30-sn/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/inovelli/vzm30-sn/can_handle.lua new file mode 100644 index 0000000000..9d1b285b45 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/inovelli/vzm30-sn/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +return function(opts, driver, device) + local INOVELLI_VZM30_SN_FINGERPRINTS = { + { mfr = "Inovelli", model = "VZM30-SN" }, + } + for _, fp in ipairs(INOVELLI_VZM30_SN_FINGERPRINTS) do + if device:get_manufacturer() == fp.mfr and device:get_model() == fp.model then + local sub_driver = require("inovelli.vzm30-sn") + return true, sub_driver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli/vzm30-sn/init.lua b/drivers/SmartThings/zigbee-switch/src/inovelli/vzm30-sn/init.lua new file mode 100644 index 0000000000..6ad3ec758d --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/inovelli/vzm30-sn/init.lua @@ -0,0 +1,53 @@ +-- Copyright 2025 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 clusters = require "st.zigbee.zcl.clusters" +local st_device = require "st.device" +local inovelli_common = require "inovelli.common" + +local TemperatureMeasurement = clusters.TemperatureMeasurement +local RelativeHumidity = clusters.RelativeHumidity + +local PRIVATE_CLUSTER_ID = 0xFC31 +local MFG_CODE = 0x122F + +local function configure_temperature_reporting(device) + local min_temp_change = 50 -- 0.5°C in 0.01°C units + device:send(TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(device, 30, 3600, min_temp_change)) +end + +local function configure_humidity_reporting(device) + local min_humidity_change = 50 -- 0.5% in 0.01% units + device:send(RelativeHumidity.attributes.MeasuredValue:configure_reporting(device, 30, 3600, min_humidity_change)) +end + +local function device_configure(driver, device) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + inovelli_common.base_device_configure(driver, device, PRIVATE_CLUSTER_ID, MFG_CODE) + configure_temperature_reporting(device) + configure_humidity_reporting(device) + else + device:configure() + end +end + +local vzm30_sn = { + NAME = "Inovelli VZM30-SN Zigbee Switch", + can_handle = require("inovelli.vzm30-sn.can_handle"), + lifecycle_handlers = { + doConfigure = device_configure, + }, +} + +return vzm30_sn \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli/vzm32-sn/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/inovelli/vzm32-sn/can_handle.lua new file mode 100644 index 0000000000..aa0004b193 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/inovelli/vzm32-sn/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +return function(opts, driver, device) + local INOVELLI_VZM32_SN_FINGERPRINTS = { + { mfr = "Inovelli", model = "VZM32-SN" }, + } + for _, fp in ipairs(INOVELLI_VZM32_SN_FINGERPRINTS) do + if device:get_manufacturer() == fp.mfr and device:get_model() == fp.model then + local sub_driver = require("inovelli.vzm32-sn") + return true, sub_driver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli/vzm32-sn/init.lua b/drivers/SmartThings/zigbee-switch/src/inovelli/vzm32-sn/init.lua new file mode 100644 index 0000000000..4e0151aeb6 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/inovelli/vzm32-sn/init.lua @@ -0,0 +1,68 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local st_device = require "st.device" +local device_management = require "st.zigbee.device_management" +local inovelli_common = require "inovelli.common" + +local OccupancySensing = clusters.OccupancySensing + +local PRIVATE_CLUSTER_ID = 0xFC31 +local MFG_CODE = 0x122F + + +local function configure_illuminance_reporting(device) + local min_lux_change = 15 + local value = math.floor(10000 * math.log(min_lux_change, 10) + 1) + device:send(clusters.IlluminanceMeasurement.attributes.MeasuredValue:configure_reporting(device, 10, 600, value)) +end + +local function refresh_handler(driver, device, command) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:refresh() + device:send(OccupancySensing.attributes.Occupancy:read(device)) + else + device:refresh() + end +end + +local function device_added(driver, device) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + refresh_handler(driver, device, {}) + else + device:emit_event(capabilities.colorControl.hue(1)) + device:emit_event(capabilities.colorControl.saturation(1)) + device:emit_event(capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = 2700, maximum = 6500} })) + device:emit_event(capabilities.colorTemperature.colorTemperature(6500)) + device:emit_event(capabilities.switchLevel.level(100)) + device:emit_event(capabilities.switch.switch("off")) + end +end + +local function device_configure(driver, device) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + inovelli_common.base_device_configure(driver, device, PRIVATE_CLUSTER_ID, MFG_CODE) + device:send(device_management.build_bind_request(device, OccupancySensing.ID, driver.environment_info.hub_zigbee_eui)) + configure_illuminance_reporting(device) + else + device:configure() + end +end + +local vzm32_sn = { + NAME = "Inovelli VZM32-SN mmWave Dimmer", + can_handle = require("inovelli.vzm32-sn.can_handle"), + lifecycle_handlers = { + added = device_added, + doConfigure = device_configure, + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = refresh_handler, + } + } +} + +return vzm32_sn \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/src/jasco/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/jasco/can_handle.lua new file mode 100644 index 0000000000..c97ff700c8 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/jasco/can_handle.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local JASCO_SWTICH_FINGERPRINTS = { + { mfr = "Jasco Products", model = "43095" }, + { mfr = "Jasco Products", model = "43132" }, + { mfr = "Jasco Products", model = "43078" } +} + + for _, fingerprint in ipairs(JASCO_SWTICH_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("jasco") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/jasco/init.lua b/drivers/SmartThings/zigbee-switch/src/jasco/init.lua index 6c90115a22..a21059dfb4 100644 --- a/drivers/SmartThings/zigbee-switch/src/jasco/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/jasco/init.lua @@ -1,38 +1,11 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" local SimpleMetering = clusters.SimpleMetering local constants = require "st.zigbee.constants" -local JASCO_SWTICH_FINGERPRINTS = { - { mfr = "Jasco Products", model = "43095" }, - { mfr = "Jasco Products", model = "43132" }, - { mfr = "Jasco Products", model = "43078" } -} - -local is_jasco_switch = function(opts, driver, device) - for _, fingerprint in ipairs(JASCO_SWTICH_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("jasco") - return true, subdriver - end - end - return false -end - local device_added = function(self, device) local customEnergyDivisor = 10000 device:set_field(constants.SIMPLE_METERING_DIVISOR_KEY, customEnergyDivisor, {persist = true}) @@ -63,7 +36,7 @@ local jasco_switch = { added = device_added, doConfigure = do_configure, }, - can_handle = is_jasco_switch + can_handle = require("jasco.can_handle"), } return jasco_switch diff --git a/drivers/SmartThings/zigbee-switch/src/laisiao/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/laisiao/can_handle.lua new file mode 100644 index 0000000000..7ed921844b --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/laisiao/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) +local FINGERPRINTS = { + { mfr = "LAISIAO", model = "yuba" }, +} + + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("laisiao") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua b/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua index edb829bf1f..2833843793 100755 --- a/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua @@ -1,34 +1,11 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local zcl_clusters = require "st.zigbee.zcl.clusters" local configurations = require "configurations" -local FINGERPRINTS = { - { mfr = "LAISIAO", model = "yuba" }, -} -local function can_handle_laisiao(opts, driver, device, ...) - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("laisiao") - return true, subdriver - end - end - return false -end local function component_to_endpoint(device, component_id) if component_id == "main" then @@ -78,7 +55,7 @@ local laisiao_bath_heater = { [capabilities.switch.commands.on.NAME] = on_handler } }, - can_handle = can_handle_laisiao + can_handle = require("laisiao.can_handle"), } return laisiao_bath_heater diff --git a/drivers/SmartThings/zigbee-switch/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-switch/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/lazy_load_subdriver.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(sub_driver_name) + -- gets the current lua libs api version + local version = require "version" + local ZigbeeDriver = require "st.zigbee" + if version.api >= 16 then + return ZigbeeDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZigbeeDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end +end diff --git a/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/device_added.lua b/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/device_added.lua index 275b7e4b27..a241ea506c 100644 --- a/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/device_added.lua +++ b/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/device_added.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local find_child = require "lifecycle_handlers.find_child" diff --git a/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/do_configure.lua b/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/do_configure.lua index 088cc604b4..465969a755 100644 --- a/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/do_configure.lua +++ b/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/do_configure.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" return function(self, device) diff --git a/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/find_child.lua b/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/find_child.lua index e33df67817..31b1640619 100644 --- a/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/find_child.lua +++ b/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/find_child.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 return function(parent, ep_id) return parent:get_child_by_parent_assigned_key(string.format("%02X", ep_id)) diff --git a/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/info_changed.lua b/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/info_changed.lua index 9699760a92..bd15064b5e 100644 --- a/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/info_changed.lua +++ b/drivers/SmartThings/zigbee-switch/src/lifecycle_handlers/info_changed.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 return function(self, device, event, args) local preferences = require "preferences" diff --git a/drivers/SmartThings/zigbee-switch/src/multi-switch-no-master/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/multi-switch-no-master/can_handle.lua new file mode 100644 index 0000000000..80cd735e93 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/multi-switch-no-master/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local FINGERPRINTS = require "multi-switch-no-master.fingerprints" + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model and (device:get_manufacturer() == nil or device:get_manufacturer() == fingerprint.mfr) then + local subdriver = require("multi-switch-no-master") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/multi-switch-no-master/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/multi-switch-no-master/fingerprints.lua new file mode 100644 index 0000000000..48dd5ac246 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/multi-switch-no-master/fingerprints.lua @@ -0,0 +1,47 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "DAWON_DNS", model = "PM-S240-ZB", children = 1 }, + { mfr = "DAWON_DNS", model = "PM-S240R-ZB", children = 1 }, + { mfr = "DAWON_DNS", model = "PM-S250-ZB", children = 1 }, + { mfr = "DAWON_DNS", model = "PM-S340-ZB", children = 2 }, + { mfr = "DAWON_DNS", model = "PM-S340R-ZB", children = 2 }, + { mfr = "DAWON_DNS", model = "PM-S350-ZB", children = 2 }, + { mfr = "DAWON_DNS", model = "ST-S250-ZB", children = 1 }, + { mfr = "DAWON_DNS", model = "ST-S350-ZB", children = 2 }, + { mfr = "ORVIBO", model = "074b3ffba5a045b7afd94c47079dd553", children = 1 }, + { mfr = "ORVIBO", model = "9f76c9f31b4c4a499e3aca0977ac4494", children = 2 }, + { mfr = "REXENSE", model = "HY0002", children = 1 }, + { mfr = "REXENSE", model = "HY0003", children = 2 }, + { mfr = "REX", model = "HY0096", children = 1 }, + { mfr = "REX", model = "HY0097", children = 2 }, + { mfr = "HEIMAN", model = "HS2SW2L-EFR-3.0", children = 1 }, + { mfr = "HEIMAN", model = "HS2SW3L-EFR-3.0", children = 2 }, + { mfr = "HEIMAN", model = "HS6SW2A-W-EF-3.0", children = 1 }, + { mfr = "HEIMAN", model = "HS6SW3A-W-EF-3.0", children = 2 }, + { mfr = "eWeLink", model = "ZB-SW02", children = 1 }, + { mfr = "eWeLink", model = "ZB-SW03", children = 2 }, + { mfr = "eWeLink", model = "ZB-SW04", children = 3 }, + { mfr = "SMARTvill", model = "SLA01", children = 0 }, + { mfr = "SMARTvill", model = "SLA02", children = 1 }, + { mfr = "SMARTvill", model = "SLA03", children = 2 }, + { mfr = "SMARTvill", model = "SLA04", children = 3 }, + { mfr = "SMARTvill", model = "SLA05", children = 4 }, + { mfr = "SMARTvill", model = "SLA06", children = 5 }, + { mfr = "ShinaSystem", model = "SBM300Z2", children = 1 }, + { mfr = "ShinaSystem", model = "SBM300Z3", children = 2 }, + { mfr = "ShinaSystem", model = "SBM300Z4", children = 3 }, + { mfr = "ShinaSystem", model = "SBM300Z5", children = 4 }, + { mfr = "ShinaSystem", model = "SBM300Z6", children = 5 }, + { mfr = "ShinaSystem", model = "SQM300Z2", children = 1 }, + { mfr = "ShinaSystem", model = "SQM300Z3", children = 2 }, + { mfr = "ShinaSystem", model = "SQM300Z4", children = 3 }, + { mfr = "ShinaSystem", model = "SQM300Z6", children = 5 }, + { model = "E220-KR2N0Z0-HA", children = 1 }, + { model = "E220-KR3N0Z0-HA", children = 2 }, + { model = "E220-KR4N0Z0-HA", children = 3 }, + { model = "E220-KR5N0Z0-HA", children = 4 }, + { model = "E220-KR6N0Z0-HA", children = 5 }, + { mfr = "JNL", model = "Y-K003-001", children = 2 } +} diff --git a/drivers/SmartThings/zigbee-switch/src/multi-switch-no-master/init.lua b/drivers/SmartThings/zigbee-switch/src/multi-switch-no-master/init.lua index bec5b1d87d..ba0ed07ae3 100644 --- a/drivers/SmartThings/zigbee-switch/src/multi-switch-no-master/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/multi-switch-no-master/init.lua @@ -1,76 +1,11 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local st_device = require "st.device" local utils = require "st.utils" local configurations = require "configurations" -local MULTI_SWITCH_NO_MASTER_FINGERPRINTS = { - { mfr = "DAWON_DNS", model = "PM-S240-ZB", children = 1 }, - { mfr = "DAWON_DNS", model = "PM-S240R-ZB", children = 1 }, - { mfr = "DAWON_DNS", model = "PM-S250-ZB", children = 1 }, - { mfr = "DAWON_DNS", model = "PM-S340-ZB", children = 2 }, - { mfr = "DAWON_DNS", model = "PM-S340R-ZB", children = 2 }, - { mfr = "DAWON_DNS", model = "PM-S350-ZB", children = 2 }, - { mfr = "DAWON_DNS", model = "ST-S250-ZB", children = 1 }, - { mfr = "DAWON_DNS", model = "ST-S350-ZB", children = 2 }, - { mfr = "ORVIBO", model = "074b3ffba5a045b7afd94c47079dd553", children = 1 }, - { mfr = "ORVIBO", model = "9f76c9f31b4c4a499e3aca0977ac4494", children = 2 }, - { mfr = "REXENSE", model = "HY0002", children = 1 }, - { mfr = "REXENSE", model = "HY0003", children = 2 }, - { mfr = "REX", model = "HY0096", children = 1 }, - { mfr = "REX", model = "HY0097", children = 2 }, - { mfr = "HEIMAN", model = "HS2SW2L-EFR-3.0", children = 1 }, - { mfr = "HEIMAN", model = "HS2SW3L-EFR-3.0", children = 2 }, - { mfr = "HEIMAN", model = "HS6SW2A-W-EF-3.0", children = 1 }, - { mfr = "HEIMAN", model = "HS6SW3A-W-EF-3.0", children = 2 }, - { mfr = "eWeLink", model = "ZB-SW02", children = 1 }, - { mfr = "eWeLink", model = "ZB-SW03", children = 2 }, - { mfr = "eWeLink", model = "ZB-SW04", children = 3 }, - { mfr = "SMARTvill", model = "SLA01", children = 0 }, - { mfr = "SMARTvill", model = "SLA02", children = 1 }, - { mfr = "SMARTvill", model = "SLA03", children = 2 }, - { mfr = "SMARTvill", model = "SLA04", children = 3 }, - { mfr = "SMARTvill", model = "SLA05", children = 4 }, - { mfr = "SMARTvill", model = "SLA06", children = 5 }, - { mfr = "ShinaSystem", model = "SBM300Z2", children = 1 }, - { mfr = "ShinaSystem", model = "SBM300Z3", children = 2 }, - { mfr = "ShinaSystem", model = "SBM300Z4", children = 3 }, - { mfr = "ShinaSystem", model = "SBM300Z5", children = 4 }, - { mfr = "ShinaSystem", model = "SBM300Z6", children = 5 }, - { mfr = "ShinaSystem", model = "SQM300Z2", children = 1 }, - { mfr = "ShinaSystem", model = "SQM300Z3", children = 2 }, - { mfr = "ShinaSystem", model = "SQM300Z4", children = 3 }, - { mfr = "ShinaSystem", model = "SQM300Z6", children = 5 }, - { model = "E220-KR2N0Z0-HA", children = 1 }, - { model = "E220-KR3N0Z0-HA", children = 2 }, - { model = "E220-KR4N0Z0-HA", children = 3 }, - { model = "E220-KR5N0Z0-HA", children = 4 }, - { model = "E220-KR6N0Z0-HA", children = 5 } -} - -local function is_multi_switch_no_master(opts, driver, device) - for _, fingerprint in ipairs(MULTI_SWITCH_NO_MASTER_FINGERPRINTS) do - if device:get_model() == fingerprint.model and (device:get_manufacturer() == nil or device:get_manufacturer() == fingerprint.mfr) then - local subdriver = require("multi-switch-no-master") - return true, subdriver - end - end - return false -end - local function get_children_amount(device) - for _, fingerprint in ipairs(MULTI_SWITCH_NO_MASTER_FINGERPRINTS) do + for _, fingerprint in ipairs(require("multi-switch-no-master.fingerprints")) do if device:get_model() == fingerprint.model then return fingerprint.children end @@ -117,8 +52,7 @@ local multi_switch_no_master = { init = configurations.power_reconfig_wrapper(device_init), added = device_added }, - can_handle = is_multi_switch_no_master + can_handle = require("multi-switch-no-master.can_handle"), } return multi_switch_no_master - diff --git a/drivers/SmartThings/zigbee-switch/src/non_zigbee_devices/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/non_zigbee_devices/can_handle.lua new file mode 100644 index 0000000000..bc394d4fa0 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/non_zigbee_devices/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local st_device = require "st.device" + + if device.network_type ~= st_device.NETWORK_TYPE_ZIGBEE and device.network_type ~= st_device.NETWORK_TYPE_CHILD then + return true, require("non_zigbee_devices") + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/non_zigbee_devices/init.lua b/drivers/SmartThings/zigbee-switch/src/non_zigbee_devices/init.lua index bc6caa8580..63876e658a 100644 --- a/drivers/SmartThings/zigbee-switch/src/non_zigbee_devices/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/non_zigbee_devices/init.lua @@ -1,31 +1,13 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- This is a patch for the zigbee-switch driver to fix https://smartthings.atlassian.net/browse/CHAD-16558 -- Several hubs were found that had zigbee switch drivers hosting zwave devices. -- This patch works around it until hubcore 0.59 is released with -- https://smartthings.atlassian.net/browse/CHAD-16552 -local st_device = require "st.device" local log = require "log" -local function can_handle(opts, driver, device) - if device.network_type ~= st_device.NETWORK_TYPE_ZIGBEE and device.network_type ~= st_device.NETWORK_TYPE_CHILD then - return true, require("non_zigbee_devices") - end - return false -end local function device_added(driver, device, event) log.info(string.format("Non zigbee device added: %s", device)) @@ -51,7 +33,7 @@ local non_zigbee_devices = { doConfigure = do_configure, infoChanged = info_changed }, - can_handle = can_handle + can_handle = require("non_zigbee_devices.can_handle"), } -return non_zigbee_devices \ No newline at end of file +return non_zigbee_devices diff --git a/drivers/SmartThings/zigbee-switch/src/preferences.lua b/drivers/SmartThings/zigbee-switch/src/preferences.lua index f0170aaf40..0e9c8f5b87 100644 --- a/drivers/SmartThings/zigbee-switch/src/preferences.lua +++ b/drivers/SmartThings/zigbee-switch/src/preferences.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" @@ -18,7 +7,10 @@ local data_types = require "st.zigbee.data_types" local devices = { AQARA_LIGHT = { - MATCHING_MATRIX = { mfr = "LUMI", model = "lumi.light.acn004" }, + MATCHING_MATRIX = { + { mfr = "LUMI", model = "lumi.light.acn004" }, + { mfr = "LUMI", model = "lumi.light.cwacn1" } + }, PARAMETERS = { ["stse.restorePowerState"] = function(device, value) return cluster_base.write_manufacturer_specific_attribute(device, 0xFCC0, @@ -39,7 +31,7 @@ local devices = { } }, AQARA_LIGHT_BULB = { - MATCHING_MATRIX = { mfr = "Aqara", model = "lumi.light.acn014" }, + MATCHING_MATRIX = {{ mfr = "Aqara", model = "lumi.light.acn014" }}, PARAMETERS = { ["stse.restorePowerState"] = function(device, value) return cluster_base.write_manufacturer_specific_attribute(device, 0xFCC0, @@ -75,12 +67,17 @@ preferences.sync_preferences = function(driver, device) end preferences.get_device_parameters = function(zigbee_device) + local mfr = zigbee_device:get_manufacturer() + local model = zigbee_device:get_model() + for _, device in pairs(devices) do - if zigbee_device:get_manufacturer() == device.MATCHING_MATRIX.mfr and - zigbee_device:get_model() == device.MATCHING_MATRIX.model then - return device.PARAMETERS + for _, fp in ipairs(device.MATCHING_MATRIX) do + if fp.mfr == mfr and fp.model == model then + return device.PARAMETERS + end end end + return nil end diff --git a/drivers/SmartThings/zigbee-switch/src/rexense/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/rexense/can_handle.lua new file mode 100644 index 0000000000..bcdc666417 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/rexense/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) +local ZIGBEE_METERING_PLUG_FINGERPRINTS = { + { mfr = "REXENSE", model = "HY0105" } -- HONYAR Outlet" +} + for _, fingerprint in ipairs(ZIGBEE_METERING_PLUG_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("rexense") + return true, subdriver + end + end + + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/rexense/init.lua b/drivers/SmartThings/zigbee-switch/src/rexense/init.lua index d0d457928e..4b16afe3b0 100644 --- a/drivers/SmartThings/zigbee-switch/src/rexense/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/rexense/init.lua @@ -1,26 +1,11 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local zcl_clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" local OnOff = zcl_clusters.OnOff -local ZIGBEE_METERING_PLUG_FINGERPRINTS = { - { mfr = "REXENSE", model = "HY0105" } -- HONYAR Outlet" -} - local function switch_on_handler(driver, device, command) device:send_to_component(command.component, OnOff.server.commands.On(device)) device:send(OnOff.server.commands.On(device):to_endpoint(0x02)) @@ -31,16 +16,6 @@ local function switch_off_handler(driver, device, command) device:send(OnOff.server.commands.Off(device):to_endpoint(0x02)) end -local function is_zigbee_metering_plug(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_METERING_PLUG_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("rexense") - return true, subdriver - end - end - - return false -end local zigbee_metering_plug = { NAME = "zigbee metering plug", @@ -50,7 +25,7 @@ local zigbee_metering_plug = { [capabilities.switch.commands.off.NAME] = switch_off_handler } }, - can_handle = is_zigbee_metering_plug + can_handle = require("rexense.can_handle"), } return zigbee_metering_plug diff --git a/drivers/SmartThings/zigbee-switch/src/rgb-bulb/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/rgb-bulb/can_handle.lua new file mode 100644 index 0000000000..e52cf32dcd --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/rgb-bulb/can_handle.lua @@ -0,0 +1,23 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) +local RGB_BULB_FINGERPRINTS = { + ["OSRAM"] = { + ["Gardenspot RGB"] = true, + ["LIGHTIFY Gardenspot RGB"] = true + }, + ["LEDVANCE"] = { + ["Outdoor Accent RGB"] = true + } +} + + + local can_handle = (RGB_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()] + if can_handle then + local subdriver = require("rgb-bulb") + return true, subdriver + else + return false + end +end diff --git a/drivers/SmartThings/zigbee-switch/src/rgb-bulb/init.lua b/drivers/SmartThings/zigbee-switch/src/rgb-bulb/init.lua index ce0f9976db..867b069bfe 100644 --- a/drivers/SmartThings/zigbee-switch/src/rgb-bulb/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/rgb-bulb/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -19,25 +8,6 @@ local OnOff = clusters.OnOff local Level = clusters.Level local ColorControl = clusters.ColorControl -local RGB_BULB_FINGERPRINTS = { - ["OSRAM"] = { - ["Gardenspot RGB"] = true, - ["LIGHTIFY Gardenspot RGB"] = true - }, - ["LEDVANCE"] = { - ["Outdoor Accent RGB"] = true - } -} - -local function can_handle_rgb_bulb(opts, driver, device) - local can_handle = (RGB_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()] - if can_handle then - local subdriver = require("rgb-bulb") - return true, subdriver - else - return false - end -end local function do_refresh(driver, device) local attributes = { @@ -66,7 +36,7 @@ local rgb_bulb = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_rgb_bulb + can_handle = require("rgb-bulb.can_handle"), } return rgb_bulb diff --git a/drivers/SmartThings/zigbee-switch/src/rgbw-bulb/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/rgbw-bulb/can_handle.lua new file mode 100644 index 0000000000..724bb6266a --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/rgbw-bulb/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local RGBW_BULB_FINGERPRINTS = require "rgbw-bulb.fingerprints" + local can_handle = (RGBW_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()] + if can_handle then + local subdriver = require("rgbw-bulb") + return true, subdriver + else + return false + end +end diff --git a/drivers/SmartThings/zigbee-switch/src/rgbw-bulb/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/rgbw-bulb/fingerprints.lua new file mode 100644 index 0000000000..c8eedcfec8 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/rgbw-bulb/fingerprints.lua @@ -0,0 +1,74 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + ["Samsung Electronics"] = { + ["SAMSUNG-ITM-Z-002"] = true + }, + ["Juno"] = { + ["ABL-LIGHT-Z-201"] = true + }, + ["AduroSmart Eria"] = { + ["AD-RGBW3001"] = true + }, + ["Aurora"] = { + ["RGBCXStrip50AU"] = true, + ["RGBGU10Bulb50AU"] = true, + ["RGBBulb51AU"] = true + }, + ["CWD"] = { + ["ZB.A806Ergbw-A001"] = true, + ["ZB.A806Brgbw-A001"] = true, + ["ZB.M350rgbw-A001"] = true + }, + ["innr"] = { + ["RB 285 C"] = true, + ["BY 285 C"] = true, + ["RB 250 C"] = true, + ["RS 230 C"] = true, + ["AE 280 C"] = true + }, + ["MLI"] = { + ["ZBT-ExtendedColor"] = true + }, + ["OSRAM"] = { + ["LIGHTIFY Flex RGBW"] = true, + ["Flex RGBW"] = true, + ["LIGHTIFY A19 RGBW"] = true, + ["LIGHTIFY BR RGBW"] = true, + ["LIGHTIFY RT RGBW"] = true, + ["LIGHTIFY FLEX OUTDOOR RGBW"] = true + }, + ["LEDVANCE"] = { + ["RT HO RGBW"] = true, + ["A19 RGBW"] = true, + ["FLEX Outdoor RGBW"] = true, + ["FLEX RGBW"] = true, + ["BR30 RGBW"] = true, + ["RT RGBW"] = true, + ["Outdoor Pathway RGBW"] = true, + ["Flex RGBW Pro"] = true + }, + ["LEEDARSON LIGHTING"] = { + ["5ZB-A806ST-Q1G"] = true + }, + ["sengled"] = { + ["E11-N1EA"] = true, + ["E12-N1E"] = true, + ["E21-N1EA"] = true, + ["E1G-G8E"] = true, + ["E11-U3E"] = true, + ["E11-U2E"] = true, + ["E1F-N5E"] = true, + ["E23-N13"] = true + }, + ["Neuhaus Lighting Group"] = { + ["ZBT-ExtendedColor"] = true + }, + ["Ajaxonline"] = { + ["AJ-RGBCCT 5 in 1"] = true + }, + ["Ajax online Ltd"] = { + ["AJ_ZB30_GU10"] = true + } +} diff --git a/drivers/SmartThings/zigbee-switch/src/rgbw-bulb/init.lua b/drivers/SmartThings/zigbee-switch/src/rgbw-bulb/init.lua index fb3a1a32f2..cfa2c81855 100644 --- a/drivers/SmartThings/zigbee-switch/src/rgbw-bulb/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/rgbw-bulb/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -20,88 +9,6 @@ local OnOff = clusters.OnOff local Level = clusters.Level local ColorControl = clusters.ColorControl -local RGBW_BULB_FINGERPRINTS = { - ["Samsung Electronics"] = { - ["SAMSUNG-ITM-Z-002"] = true - }, - ["Juno"] = { - ["ABL-LIGHT-Z-201"] = true - }, - ["AduroSmart Eria"] = { - ["AD-RGBW3001"] = true - }, - ["Aurora"] = { - ["RGBCXStrip50AU"] = true, - ["RGBGU10Bulb50AU"] = true, - ["RGBBulb51AU"] = true - }, - ["CWD"] = { - ["ZB.A806Ergbw-A001"] = true, - ["ZB.A806Brgbw-A001"] = true, - ["ZB.M350rgbw-A001"] = true - }, - ["innr"] = { - ["RB 285 C"] = true, - ["BY 285 C"] = true, - ["RB 250 C"] = true, - ["RS 230 C"] = true, - ["AE 280 C"] = true - }, - ["MLI"] = { - ["ZBT-ExtendedColor"] = true - }, - ["OSRAM"] = { - ["LIGHTIFY Flex RGBW"] = true, - ["Flex RGBW"] = true, - ["LIGHTIFY A19 RGBW"] = true, - ["LIGHTIFY BR RGBW"] = true, - ["LIGHTIFY RT RGBW"] = true, - ["LIGHTIFY FLEX OUTDOOR RGBW"] = true - }, - ["LEDVANCE"] = { - ["RT HO RGBW"] = true, - ["A19 RGBW"] = true, - ["FLEX Outdoor RGBW"] = true, - ["FLEX RGBW"] = true, - ["BR30 RGBW"] = true, - ["RT RGBW"] = true, - ["Outdoor Pathway RGBW"] = true, - ["Flex RGBW Pro"] = true - }, - ["LEEDARSON LIGHTING"] = { - ["5ZB-A806ST-Q1G"] = true - }, - ["sengled"] = { - ["E11-N1EA"] = true, - ["E12-N1E"] = true, - ["E21-N1EA"] = true, - ["E1G-G8E"] = true, - ["E11-U3E"] = true, - ["E11-U2E"] = true, - ["E1F-N5E"] = true, - ["E23-N13"] = true - }, - ["Neuhaus Lighting Group"] = { - ["ZBT-ExtendedColor"] = true - }, - ["Ajaxonline"] = { - ["AJ-RGBCCT 5 in 1"] = true - }, - ["Ajax online Ltd"] = { - ["AJ_ZB30_GU10"] = true - } -} - -local function can_handle_rgbw_bulb(opts, driver, device) - local can_handle = (RGBW_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()] - if can_handle then - local subdriver = require("rgbw-bulb") - return true, subdriver - else - return false - end -end - local function do_refresh(driver, device) local attributes = { OnOff.attributes.OnOff, @@ -149,7 +56,7 @@ local rgbw_bulb = { doConfigure = do_configure, added = do_added }, - can_handle = can_handle_rgbw_bulb + can_handle = require("rgbw-bulb.can_handle"), } return rgbw_bulb diff --git a/drivers/SmartThings/zigbee-switch/src/robb/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/robb/can_handle.lua new file mode 100644 index 0000000000..480bc83ab5 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/robb/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) +local ROBB_DIMMER_FINGERPRINTS = { + { mfr = "ROBB smarrt", model = "ROB_200-011-0" }, + { mfr = "ROBB smarrt", model = "ROB_200-014-0" } +} + for _, fingerprint in ipairs(ROBB_DIMMER_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("robb") + return true, subdriver + end + end + return false +end + diff --git a/drivers/SmartThings/zigbee-switch/src/robb/init.lua b/drivers/SmartThings/zigbee-switch/src/robb/init.lua index 4593b4339f..1a8c2948c3 100644 --- a/drivers/SmartThings/zigbee-switch/src/robb/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/robb/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local constants = require "st.zigbee.constants" local zcl_clusters = require "st.zigbee.zcl.clusters" @@ -18,27 +7,12 @@ local capabilities = require "st.capabilities" local configurations = require "configurations" local SimpleMetering = zcl_clusters.SimpleMetering -local ROBB_DIMMER_FINGERPRINTS = { - { mfr = "ROBB smarrt", model = "ROB_200-011-0" }, - { mfr = "ROBB smarrt", model = "ROB_200-014-0" } -} - -local function is_robb_dimmer(opts, driver, device) - for _, fingerprint in ipairs(ROBB_DIMMER_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("robb") - return true, subdriver - end - end - return false -end local do_init = function(driver, device) device:set_field(constants.SIMPLE_METERING_DIVISOR_KEY, 1000000, {persist = true}) device:set_field(constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, 10, {persist = true}) end - local function power_meter_handler(driver, device, value, zb_rx) local raw_value = value.value local multiplier = device:get_field(constants.ELECTRICAL_MEASUREMENT_MULTIPLIER_KEY) or 1 @@ -60,7 +34,7 @@ local robb_dimmer_handler = { lifecycle_handlers = { init = configurations.power_reconfig_wrapper(do_init) }, - can_handle = is_robb_dimmer + can_handle = require("robb.can_handle"), } return robb_dimmer_handler diff --git a/drivers/SmartThings/zigbee-switch/src/sinope-dimmer/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/sinope-dimmer/can_handle.lua new file mode 100644 index 0000000000..3a7233e6b9 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/sinope-dimmer/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local can_handle = device:get_manufacturer() == "Sinope Technologies" and device:get_model() == "DM2500ZB" + if can_handle then + local subdriver = require("sinope-dimmer") + return true, subdriver + else + return false + end + end diff --git a/drivers/SmartThings/zigbee-switch/src/sinope-dimmer/init.lua b/drivers/SmartThings/zigbee-switch/src/sinope-dimmer/init.lua index 981df6ff5e..d97c873467 100644 --- a/drivers/SmartThings/zigbee-switch/src/sinope-dimmer/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/sinope-dimmer/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local cluster_base = require "st.zigbee.cluster_base" @@ -104,15 +93,7 @@ local zigbee_sinope_dimmer = { lifecycle_handlers = { infoChanged = info_changed }, - can_handle = function(opts, driver, device, ...) - local can_handle = device:get_manufacturer() == "Sinope Technologies" and device:get_model() == "DM2500ZB" - if can_handle then - local subdriver = require("sinope-dimmer") - return true, subdriver - else - return false - end - end + can_handle = require("sinope-dimmer.can_handle"), } return zigbee_sinope_dimmer diff --git a/drivers/SmartThings/zigbee-switch/src/sinope/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/sinope/can_handle.lua new file mode 100644 index 0000000000..624baee756 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/sinope/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local can_handle = device:get_manufacturer() == "Sinope Technologies" and device:get_model() == "SW2500ZB" + if can_handle then + local subdriver = require("sinope") + return true, subdriver + else + return false + end + end diff --git a/drivers/SmartThings/zigbee-switch/src/sinope/init.lua b/drivers/SmartThings/zigbee-switch/src/sinope/init.lua index d3e34513a5..af0300a377 100644 --- a/drivers/SmartThings/zigbee-switch/src/sinope/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/sinope/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" @@ -41,15 +30,7 @@ local zigbee_sinope_switch = { lifecycle_handlers = { infoChanged = info_changed }, - can_handle = function(opts, driver, device, ...) - local can_handle = device:get_manufacturer() == "Sinope Technologies" and device:get_model() == "SW2500ZB" - if can_handle then - local subdriver = require("sinope") - return true, subdriver - else - return false - end - end + can_handle = require("sinope.can_handle"), } return zigbee_sinope_switch diff --git a/drivers/SmartThings/zigbee-switch/src/switch_utils.lua b/drivers/SmartThings/zigbee-switch/src/switch_utils.lua index 7f5429dceb..66ad4715f9 100644 --- a/drivers/SmartThings/zigbee-switch/src/switch_utils.lua +++ b/drivers/SmartThings/zigbee-switch/src/switch_utils.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local switch_utils = {} diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_all_capability_zigbee_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_all_capability_zigbee_bulb.lua index 319066fa3b..70dfc96780 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_all_capability_zigbee_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_all_capability_zigbee_bulb.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_led_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_led_bulb.lua index 9c7a7cc0e5..b2f9359850 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_led_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_led_bulb.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" @@ -18,6 +7,7 @@ local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local capabilities = require "st.capabilities" local OnOff = clusters.OnOff local Level = clusters.Level @@ -48,6 +38,7 @@ zigbee_test_utils.prepare_zigbee_env_info() local function test_init() test.mock_device.add_test_device(mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ minimum = 2700, maximum = 6000 }))) end test.set_test_init_function(test_init) @@ -61,6 +52,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) + -- test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ minimum = 2700, maximum = 6000 }))) end ) diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua index 68f54c986f..940131b3e1 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" @@ -18,6 +7,7 @@ local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local capabilities = require "st.capabilities" local OnOff = clusters.OnOff local Level = clusters.Level @@ -50,6 +40,7 @@ zigbee_test_utils.prepare_zigbee_env_info() local function test_init() test.mock_device.add_test_device(mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ minimum = 2700, maximum = 6000 }))) end test.set_test_init_function(test_init) @@ -63,6 +54,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) + -- test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ minimum = 2700, maximum = 6000 }))) end ) diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_smart_plug.lua b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_smart_plug.lua index d9edfbe425..0d51bd11c2 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_smart_plug.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_smart_plug.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_smart_plug_t1.lua b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_smart_plug_t1.lua index c1ba5bef7d..8a544caf13 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_smart_plug_t1.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_smart_plug_t1.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_module.lua b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_module.lua index e0038602fb..f27d84cfc6 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_module.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_module.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_module_no_power.lua b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_module_no_power.lua index 6ed61191da..840aadacb9 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_module_no_power.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_module_no_power.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_no_power.lua b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_no_power.lua index e35a1c25e5..207e7bf21b 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_no_power.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_no_power.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_power.lua b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_power.lua index 8b596191a5..a02ef37aa2 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_power.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_power.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_wall_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_wall_switch.lua index b47d0595c8..3711116d99 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_wall_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_wall_switch.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_aurora_relay.lua b/drivers/SmartThings/zigbee-switch/src/test/test_aurora_relay.lua index 6dd6ed75b7..57d6576955 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aurora_relay.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aurora_relay.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_bad_data_type.lua b/drivers/SmartThings/zigbee-switch/src/test/test_bad_data_type.lua index 69ee7f043a..b30f49df23 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_bad_data_type.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_bad_data_type.lua @@ -1,17 +1,5 @@ - --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_bad_device_kind.lua b/drivers/SmartThings/zigbee-switch/src/test/test_bad_device_kind.lua index 3f66d675c9..bd8ce15b10 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_bad_device_kind.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_bad_device_kind.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_cree_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_cree_bulb.lua index e971201f85..8841c5a53d 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_cree_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_cree_bulb.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_duragreen_color_temp_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_duragreen_color_temp_bulb.lua index 0dee55ebf4..55c491e77d 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_duragreen_color_temp_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_duragreen_color_temp_bulb.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_enbrighten_metering_dimmer.lua b/drivers/SmartThings/zigbee-switch/src/test/test_enbrighten_metering_dimmer.lua index 3b8c7187b4..3b5a5b93b1 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_enbrighten_metering_dimmer.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_enbrighten_metering_dimmer.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_frient_IO_module.lua b/drivers/SmartThings/zigbee-switch/src/test/test_frient_IO_module.lua new file mode 100644 index 0000000000..c9d5f9b75e --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_frient_IO_module.lua @@ -0,0 +1,683 @@ +-- Copyright 2025 SmartThings +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" + +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local data_types = require "st.zigbee.data_types" +local cluster_base = require "st.zigbee.cluster_base" +local messages = require "st.zigbee.messages" +local constants = require "st.zigbee.constants" +local zdo_messages = require "st.zigbee.zdo" +local bind_request = require "st.zigbee.zdo.bind_request" +local unbind_request = require "frient-IO.unbind_request" +local default_response = require "st.zigbee.zcl.global_commands.default_response" +local zcl_messages = require "st.zigbee.zcl" +local Status = require "st.zigbee.generated.types.ZclStatus" +local device_management = require "st.zigbee.device_management" +local configuration_map = require "configurations" +local switch_defaults = require "st.zigbee.defaults.switch_defaults" +local mock_devices_api = require "integration_test.mock_devices_api" + +local BasicInput = clusters.BasicInput +local OnOff = clusters.OnOff +local Switch = capabilities.switch + +local ZIGBEE_ENDPOINTS = { + INPUT_1 = 0x70, + INPUT_2 = 0x71, + INPUT_3 = 0x72, + INPUT_4 = 0x73, + OUTPUT_1 = 0x74, + OUTPUT_2 = 0x75, +} + +local INPUT_CONFIGS = { + { + endpoint = ZIGBEE_ENDPOINTS.INPUT_1, + binds = { + ZIGBEE_ENDPOINTS.OUTPUT_1, + ZIGBEE_ENDPOINTS.OUTPUT_2, + }, + }, + { + endpoint = ZIGBEE_ENDPOINTS.INPUT_2, + binds = { + ZIGBEE_ENDPOINTS.OUTPUT_1, + ZIGBEE_ENDPOINTS.OUTPUT_2, + }, + }, + { + endpoint = ZIGBEE_ENDPOINTS.INPUT_3, + binds = { + ZIGBEE_ENDPOINTS.OUTPUT_1, + ZIGBEE_ENDPOINTS.OUTPUT_2, + }, + }, + { + endpoint = ZIGBEE_ENDPOINTS.INPUT_4, + binds = { + ZIGBEE_ENDPOINTS.OUTPUT_1, + ZIGBEE_ENDPOINTS.OUTPUT_2, + }, + }, +} + +local DEVELCO_MFG_CODE = 0x1015 +local ON_TIME_ATTR = 0x8000 +local OFF_WAIT_ATTR = 0x8001 + +local function sanitize_timing(value) + local v = tonumber(value) or 0 + if v < 0 then + v = 0 + elseif v > 0xFFFF then + v = 0xFFFF + end + return math.tointeger(v) or 0 +end + +local function to_deciseconds(value) + return math.floor(sanitize_timing(value) * 10) +end + +local function build_client_mfg_write(device, endpoint, attr_id, value) + local msg = cluster_base.write_manufacturer_specific_attribute( + device, + BasicInput.ID, + attr_id, + DEVELCO_MFG_CODE, + data_types.Uint16, + value + ) + msg.body.zcl_header.frame_ctrl:set_direction_client() + msg.tx_options = data_types.Uint16(0) + return msg:to_endpoint(endpoint) +end + +local function build_basic_input_polarity_write(device, endpoint, enabled) + local polarity_value = data_types.validate_or_build_type( + enabled and 1 or 0, + BasicInput.attributes.Polarity.base_type, + "payload" + ) + local msg = cluster_base.write_attribute( + device, + data_types.ClusterId(BasicInput.ID), + data_types.AttributeId(BasicInput.attributes.Polarity.ID), + polarity_value + ) + msg.tx_options = data_types.Uint16(0) + return msg:to_endpoint(endpoint) +end + +local function build_bind(device, src_ep, dest_ep) + local addr_header = messages.AddressHeader( + constants.HUB.ADDR, + constants.HUB.ENDPOINT, + device:get_short_address(), + device.fingerprinted_endpoint_id, + constants.ZDO_PROFILE_ID, + bind_request.BindRequest.ID + ) + local bind_body = bind_request.BindRequest( + device.zigbee_eui, + src_ep, + BasicInput.ID, + bind_request.ADDRESS_MODE_64_BIT, + device.zigbee_eui, + dest_ep + ) + local message_body = zdo_messages.ZdoMessageBody({ zdo_body = bind_body }) + local msg = messages.ZigbeeMessageTx({ address_header = addr_header, body = message_body }) + msg.tx_options = data_types.Uint16(0) + return msg +end + +local function build_unbind(device, src_ep, dest_ep) + local addr_header = messages.AddressHeader( + constants.HUB.ADDR, + constants.HUB.ENDPOINT, + device:get_short_address(), + device.fingerprinted_endpoint_id, + constants.ZDO_PROFILE_ID, + unbind_request.UNBIND_REQUEST_CLUSTER_ID + ) + local unbind_body = unbind_request.UnbindRequest( + device.zigbee_eui, + src_ep, + BasicInput.ID, + unbind_request.ADDRESS_MODE_64_BIT, + device.zigbee_eui, + dest_ep + ) + local message_body = zdo_messages.ZdoMessageBody({ zdo_body = unbind_body }) + local msg = messages.ZigbeeMessageTx({ address_header = addr_header, body = message_body }) + msg.tx_options = data_types.Uint16(0) + return msg +end + +local function build_default_response_msg(device, endpoint, command_id) + local addr_header = messages.AddressHeader( + device:get_short_address(), + endpoint, + constants.HUB.ADDR, + constants.HUB.ENDPOINT, + constants.HA_PROFILE_ID, + OnOff.ID + ) + local response_body = default_response.DefaultResponse(command_id, Status.SUCCESS) + local zcl_header = zcl_messages.ZclHeader({ + cmd = data_types.ZCLCommandId(response_body.ID) + }) + local message_body = zcl_messages.ZclMessageBody({ + zcl_header = zcl_header, + zcl_body = response_body + }) + return messages.ZigbeeMessageRx({ address_header = addr_header, body = message_body }) +end + +local function build_output_timing(device, child, suffix) + local on_pref + local off_pref + if child.preferences.configOnTime ~= nil or child.preferences.configOffWaitTime ~= nil then + on_pref = child.preferences.configOnTime or 0 + off_pref = child.preferences.configOffWaitTime or 0 + else + on_pref = device.preferences["configOnTime" .. suffix] or 0 + off_pref = device.preferences["configOffWaitTime" .. suffix] or 0 + end + return to_deciseconds(on_pref), to_deciseconds(off_pref) +end + +local function copy_table(source) + local result = {} + for key, value in pairs(source) do + result[key] = value + end + return result +end + +local parent_preference_state = {} + +local mock_parent_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("switch-4inputs-2outputs.yml"), + fingerprinted_endpoint_id = ZIGBEE_ENDPOINTS.INPUT_1, + label = "frient IO Module", + zigbee_endpoints = { + [ZIGBEE_ENDPOINTS.INPUT_1] = { + id = ZIGBEE_ENDPOINTS.INPUT_1, + manufacturer = "frient A/S", + model = "IOMZB-110", + server_clusters = { BasicInput.ID }, + }, + [ZIGBEE_ENDPOINTS.INPUT_2] = { + id = ZIGBEE_ENDPOINTS.INPUT_2, + manufacturer = "frient A/S", + model = "IOMZB-110", + server_clusters = { BasicInput.ID }, + }, + [ZIGBEE_ENDPOINTS.INPUT_3] = { + id = ZIGBEE_ENDPOINTS.INPUT_3, + manufacturer = "frient A/S", + model = "IOMZB-110", + server_clusters = { BasicInput.ID }, + }, + [ZIGBEE_ENDPOINTS.INPUT_4] = { + id = ZIGBEE_ENDPOINTS.INPUT_4, + manufacturer = "frient A/S", + model = "IOMZB-110", + server_clusters = { BasicInput.ID }, + }, + [ZIGBEE_ENDPOINTS.OUTPUT_1] = { + id = ZIGBEE_ENDPOINTS.OUTPUT_1, + manufacturer = "frient A/S", + model = "IOMZB-110", + server_clusters = { OnOff.ID, BasicInput.ID }, + }, + [ZIGBEE_ENDPOINTS.OUTPUT_2] = { + id = ZIGBEE_ENDPOINTS.OUTPUT_2, + manufacturer = "frient A/S", + model = "IOMZB-110", + server_clusters = { OnOff.ID, BasicInput.ID }, + }, + }, +}) + + function mock_parent_device:get_model() + return "IOMZB-110" + end + + function mock_parent_device:get_manufacturer() + return "frient A/S" + end + + function mock_parent_device:supports_server_cluster(cluster_id, endpoint_id) + local function endpoint_supports(ep) + if not ep or not ep.server_clusters then return false end + for _, server_cluster in ipairs(ep.server_clusters) do + if server_cluster == cluster_id then + return true + end + end + return false + end + + if endpoint_id ~= nil then + return endpoint_supports(self.zigbee_endpoints[endpoint_id]) + end + + for _, endpoint in pairs(self.zigbee_endpoints) do + if endpoint_supports(endpoint) then + return true + end + end + return false + end + +local mock_output_child_1 = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("frient-io-output-switch.yml"), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = "frient-io-output-1", + label = "frient IO Module Output 1", + vendor_provided_label = "Output 1", +}) + +local mock_output_child_2 = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("frient-io-output-switch.yml"), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = "frient-io-output-2", + label = "frient IO Module Output 2", + vendor_provided_label = "Output 2", +}) + +local function reset_preferences() + mock_parent_device.preferences.reversePolarity1 = false + mock_parent_device.preferences.reversePolarity2 = false + mock_parent_device.preferences.reversePolarity3 = false + mock_parent_device.preferences.reversePolarity4 = false + + mock_parent_device.preferences.controlOutput11 = false + mock_parent_device.preferences.controlOutput21 = false + mock_parent_device.preferences.controlOutput12 = false + mock_parent_device.preferences.controlOutput22 = false + mock_parent_device.preferences.controlOutput13 = false + mock_parent_device.preferences.controlOutput23 = false + mock_parent_device.preferences.controlOutput14 = false + mock_parent_device.preferences.controlOutput24 = false + + mock_parent_device.preferences.configOnTime1 = 3 + mock_parent_device.preferences.configOffWaitTime1 = 4 + mock_parent_device.preferences.configOnTime2 = 7 + mock_parent_device.preferences.configOffWaitTime2 = 8 + + mock_output_child_1.preferences.configOnTime = 5 + mock_output_child_1.preferences.configOffWaitTime = 6 + mock_output_child_2.preferences.configOnTime = 0 + mock_output_child_2.preferences.configOffWaitTime = 0 + + parent_preference_state = copy_table(mock_parent_device.preferences) + + local field_keys = { + "frient_io_native_70", + "frient_io_native_71", + "frient_io_native_72", + "frient_io_native_73", + "frient_io_native_74", + "frient_io_native_75", + } + + for _, key in ipairs(field_keys) do + mock_parent_device:set_field(key, nil, { persist = true }) + end + + mock_output_child_1:set_field("frient_io_native_74", nil, { persist = true }) + mock_output_child_2:set_field("frient_io_native_75", nil, { persist = true }) +end + +local function queue_child_info_changed(child, preferences) + local raw = rawget(child, "raw_st_data") + if raw and raw.preferences then + for key, value in pairs(preferences) do + raw.preferences[key] = value + end + end + test.socket.device_lifecycle:__queue_receive(child:generate_info_changed({ preferences = preferences })) +end + +local function queue_parent_info_changed(preferences) + local full_preferences = copy_table(parent_preference_state) + for key, value in pairs(preferences) do + full_preferences[key] = value + end + parent_preference_state = copy_table(full_preferences) + + local raw = rawget(mock_parent_device, "raw_st_data") + if raw and raw.preferences then + for key, value in pairs(full_preferences) do + raw.preferences[key] = value + end + end + + test.socket.device_lifecycle:__queue_receive( + mock_parent_device:generate_info_changed({ preferences = full_preferences }) + ) +end + +local function register_initial_config_expectations() + if test.socket.zigbee and test.socket.zigbee.__set_channel_ordering then + test.socket.zigbee:__set_channel_ordering("relaxed") + end + if test.socket.devices and test.socket.devices.__set_channel_ordering then + test.socket.devices:__set_channel_ordering("relaxed") + end + + local function register_device_configure_expectations() + local configuration = configuration_map.get_device_configuration(mock_parent_device) or {} + local configs_by_cluster = {} + local function add_attribute_config(attribute) + if attribute.configurable ~= false then + configs_by_cluster[attribute.cluster] = configs_by_cluster[attribute.cluster] or {} + table.insert(configs_by_cluster[attribute.cluster], attribute) + end + end + + for _, attribute in ipairs(configuration) do + add_attribute_config(attribute) + end + + local default_configs = switch_defaults.attribute_configurations or {} + for _, attribute in ipairs(default_configs) do + add_attribute_config(attribute) + end + + local cluster_ids = {} + for cluster_id in pairs(configs_by_cluster) do + cluster_ids[#cluster_ids + 1] = cluster_id + end + table.sort(cluster_ids) + + local endpoint_ids = {} + for endpoint_id in pairs(mock_parent_device.zigbee_endpoints) do + endpoint_ids[#endpoint_ids + 1] = endpoint_id + end + table.sort(endpoint_ids) + + for _, cluster_id in ipairs(cluster_ids) do + local attr_configs = configs_by_cluster[cluster_id] + table.sort(attr_configs, function(a, b) + return a.attribute < b.attribute + end) + for _, endpoint_id in ipairs(endpoint_ids) do + local endpoint = mock_parent_device.zigbee_endpoints[endpoint_id] + if endpoint and mock_parent_device:supports_server_cluster(cluster_id, endpoint.id) then + local bind_cmd = device_management.build_bind_request( + mock_parent_device, + cluster_id, + zigbee_test_utils.mock_hub_eui, + endpoint.id + ):to_endpoint(endpoint.id) + bind_cmd.tx_options = data_types.Uint16(0) + test.socket.zigbee:__expect_send({ mock_parent_device.id, bind_cmd }) + for _, attr_config in ipairs(attr_configs) do + local config_cmd = device_management.attr_config(mock_parent_device, attr_config):to_endpoint(endpoint.id) + config_cmd.tx_options = data_types.Uint16(0) + test.socket.zigbee:__expect_send({ mock_parent_device.id, config_cmd }) + end + end + end + end + end + + register_device_configure_expectations() + + local on1, off1 = build_output_timing(mock_parent_device, mock_output_child_1, "1") + local on2, off2 = build_output_timing(mock_parent_device, mock_output_child_2, "2") + + local function enqueue_output_timing_writes() + test.socket.zigbee:__expect_send({ mock_parent_device.id, build_client_mfg_write(mock_parent_device, ZIGBEE_ENDPOINTS.OUTPUT_1, ON_TIME_ATTR, on1) }) + test.socket.zigbee:__expect_send({ mock_parent_device.id, build_client_mfg_write(mock_parent_device, ZIGBEE_ENDPOINTS.OUTPUT_1, OFF_WAIT_ATTR, off1) }) + test.socket.zigbee:__expect_send({ mock_parent_device.id, build_client_mfg_write(mock_parent_device, ZIGBEE_ENDPOINTS.OUTPUT_2, ON_TIME_ATTR, on2) }) + test.socket.zigbee:__expect_send({ mock_parent_device.id, build_client_mfg_write(mock_parent_device, ZIGBEE_ENDPOINTS.OUTPUT_2, OFF_WAIT_ATTR, off2) }) + end + + -- Device init issues one set of manufacturer-specific writes per output during startup + enqueue_output_timing_writes() + + for _, config in ipairs(INPUT_CONFIGS) do + test.socket.zigbee:__expect_send({ mock_parent_device.id, build_basic_input_polarity_write(mock_parent_device, config.endpoint, false) }) + for _, output_ep in ipairs(config.binds) do + test.socket.zigbee:__expect_send({ mock_parent_device.id, build_unbind(mock_parent_device, config.endpoint, output_ep) }) + end + end +end + +local function expect_init_sequence() + mock_devices_api.__expect_update_device( + mock_parent_device.id, + { deviceId = mock_parent_device.id, provisioningState = "PROVISIONED" } + ) + test.socket.device_lifecycle:__queue_receive({ mock_parent_device.id, "doConfigure" }) +end + +local function expect_switch_registration(device) + test.socket.devices:__expect_send({ + "register_native_capability_attr_handler", + { device_uuid = device.id, capability_id = "switch", capability_attr_id = "switch" }, + }) +end + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + reset_preferences() + register_initial_config_expectations() + test.mock_device.add_test_device(mock_parent_device) + test.mock_device.add_test_device(mock_output_child_1) + test.mock_device.add_test_device(mock_output_child_2) + zigbee_test_utils.init_noop_health_check_timer() + --register_initial_config_expectations() +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Init configures outputs and routes attribute reports", + function() + expect_init_sequence() + test.wait_for_events() + + test.socket.capability:__set_channel_ordering("relaxed") + + test.socket.zigbee:__queue_receive({ + mock_parent_device.id, + OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, true):from_endpoint(ZIGBEE_ENDPOINTS.OUTPUT_1), + }) + test.socket.capability:__expect_send(mock_output_child_1:generate_test_message("main", Switch.switch.on())) + expect_switch_registration(mock_output_child_1) + + test.socket.zigbee:__queue_receive({ + mock_parent_device.id, + OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, false):from_endpoint(ZIGBEE_ENDPOINTS.OUTPUT_2), + }) + test.socket.capability:__expect_send(mock_output_child_2:generate_test_message("main", Switch.switch.off())) + expect_switch_registration(mock_output_child_2) + + test.socket.zigbee:__queue_receive({ + mock_parent_device.id, + BasicInput.attributes.PresentValue:build_test_attr_report(mock_parent_device, true):from_endpoint(ZIGBEE_ENDPOINTS.INPUT_3), + }) + test.socket.capability:__expect_send(mock_parent_device:generate_test_message("input3", Switch.switch.on())) + + test.wait_for_events() + + local child1_native = mock_output_child_1:get_field("frient_io_native_74") + assert(child1_native, "expected Output 1 child to register native switch handler") + local child2_native = mock_output_child_2:get_field("frient_io_native_75") + assert(child2_native, "expected Output 2 child to register native switch handler") + local parent_native = mock_parent_device:get_field("frient_io_native_72") + assert(parent_native, "expected parent device to register native switch handler for input 3") + end +) + +test.register_coroutine_test( + "Default responses update state and trigger reads", + function() + expect_init_sequence() + test.wait_for_events() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__set_channel_ordering("relaxed") + + local on_response = build_default_response_msg(mock_parent_device, ZIGBEE_ENDPOINTS.OUTPUT_1, OnOff.server.commands.On.ID) + test.socket.zigbee:__queue_receive({ mock_parent_device.id, on_response }) + test.socket.capability:__expect_send(mock_output_child_1:generate_test_message("main", Switch.switch.on())) + + local timed_response = build_default_response_msg(mock_parent_device, ZIGBEE_ENDPOINTS.OUTPUT_1, OnOff.server.commands.OnWithTimedOff.ID) + test.socket.zigbee:__queue_receive({ mock_parent_device.id, timed_response }) + local read_msg = cluster_base.read_attribute( + mock_parent_device, + data_types.ClusterId(OnOff.ID), + data_types.AttributeId(OnOff.attributes.OnOff.ID) + ) + read_msg.tx_options = data_types.Uint16(0) + read_msg = read_msg:to_endpoint(ZIGBEE_ENDPOINTS.OUTPUT_1) + test.socket.zigbee:__expect_send({ mock_parent_device.id, read_msg }) + + local off_response = build_default_response_msg(mock_parent_device, ZIGBEE_ENDPOINTS.OUTPUT_1, OnOff.server.commands.Off.ID) + test.socket.zigbee:__queue_receive({ mock_parent_device.id, off_response }) + test.socket.capability:__expect_send(mock_output_child_1:generate_test_message("main", Switch.switch.off())) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Switch commands drive the correct Zigbee commands", + function() + expect_init_sequence() + test.wait_for_events() + test.socket.zigbee:__set_channel_ordering("relaxed") + + test.socket.capability:__queue_receive({ + mock_output_child_1.id, + { capability = "switch", component = "main", command = "on", args = {} }, + }) + local on1, off1 = build_output_timing(mock_parent_device, mock_output_child_1, "1") + local timed_on = OnOff.server.commands.OnWithTimedOff( + mock_parent_device, + data_types.Uint8(0), + data_types.Uint16(on1), + data_types.Uint16(off1) + ):to_endpoint(ZIGBEE_ENDPOINTS.OUTPUT_1) + test.socket.zigbee:__expect_send({ mock_parent_device.id, timed_on }) + + test.socket.capability:__queue_receive({ + mock_output_child_1.id, + { capability = "switch", component = "main", command = "off", args = {} }, + }) + local direct_off_output1 = OnOff.server.commands.Off(mock_parent_device):to_endpoint(ZIGBEE_ENDPOINTS.OUTPUT_1) + test.socket.zigbee:__expect_send({ mock_parent_device.id, direct_off_output1 }) + + test.socket.capability:__queue_receive({ + mock_output_child_2.id, + { capability = "switch", component = "main", command = "on", args = {} }, + }) + local direct_on = OnOff.server.commands.On(mock_parent_device):to_endpoint(ZIGBEE_ENDPOINTS.OUTPUT_2) + test.socket.zigbee:__expect_send({ mock_parent_device.id, direct_on }) + + test.socket.capability:__queue_receive({ + mock_output_child_2.id, + { capability = "switch", component = "main", command = "off", args = {} }, + }) + local direct_off = OnOff.server.commands.Off(mock_parent_device):to_endpoint(ZIGBEE_ENDPOINTS.OUTPUT_2) + test.socket.zigbee:__expect_send({ mock_parent_device.id, direct_off }) + + test.socket.capability:__queue_receive({ + mock_parent_device.id, + { capability = "switch", component = "output1", command = "on", args = {} }, + }) + test.socket.zigbee:__expect_send({ mock_parent_device.id, timed_on }) + + test.socket.capability:__queue_receive({ + mock_parent_device.id, + { capability = "switch", component = "output2", command = "off", args = {} }, + }) + test.socket.zigbee:__expect_send({ mock_parent_device.id, direct_off }) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Child preference changes send manufacturer writes", + function() + expect_init_sequence() + test.wait_for_events() + test.socket.zigbee:__set_channel_ordering("relaxed") + + queue_child_info_changed(mock_output_child_1, { configOnTime = 12, configOffWaitTime = 13 }) + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + build_client_mfg_write(mock_parent_device, ZIGBEE_ENDPOINTS.OUTPUT_1, ON_TIME_ATTR, to_deciseconds(12)), + }) + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + build_client_mfg_write(mock_parent_device, ZIGBEE_ENDPOINTS.OUTPUT_1, OFF_WAIT_ATTR, to_deciseconds(13)), + }) + + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Parent preference changes manage polarity and binds", + function() + expect_init_sequence() + test.wait_for_events() + test.socket.zigbee:__set_channel_ordering("relaxed") + + queue_parent_info_changed({ + reversePolarity1 = true, + controlOutput11 = true, + controlOutput21 = true, + }) + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + build_basic_input_polarity_write(mock_parent_device, ZIGBEE_ENDPOINTS.INPUT_1, true), + }) + test.socket.zigbee:__expect_send({ mock_parent_device.id, build_bind(mock_parent_device, ZIGBEE_ENDPOINTS.INPUT_1, ZIGBEE_ENDPOINTS.OUTPUT_1) }) + test.socket.zigbee:__expect_send({ mock_parent_device.id, build_bind(mock_parent_device, ZIGBEE_ENDPOINTS.INPUT_1, ZIGBEE_ENDPOINTS.OUTPUT_2) }) + test.wait_for_events() + + queue_parent_info_changed({ + reversePolarity1 = true, + controlOutput11 = false, + controlOutput21 = true, + }) + test.socket.zigbee:__expect_send({ mock_parent_device.id, build_unbind(mock_parent_device, ZIGBEE_ENDPOINTS.INPUT_1, ZIGBEE_ENDPOINTS.OUTPUT_1) }) + test.wait_for_events() + + queue_parent_info_changed({ + reversePolarity3 = true, + controlOutput23 = true, + }) + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + build_basic_input_polarity_write(mock_parent_device, ZIGBEE_ENDPOINTS.INPUT_3, true), + }) + test.socket.zigbee:__expect_send({ mock_parent_device.id, build_bind(mock_parent_device, ZIGBEE_ENDPOINTS.INPUT_3, ZIGBEE_ENDPOINTS.OUTPUT_2) }) + test.wait_for_events() + + queue_parent_info_changed({ + reversePolarity3 = true, + controlOutput23 = false, + }) + test.socket.zigbee:__expect_send({ mock_parent_device.id, build_unbind(mock_parent_device, ZIGBEE_ENDPOINTS.INPUT_3, ZIGBEE_ENDPOINTS.OUTPUT_2) }) + test.wait_for_events() + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_frient_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_frient_switch.lua index 8b9b26ba13..4290d6ebbe 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_frient_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_frient_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_ge_link_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_ge_link_bulb.lua index cdcd49c616..d37dafe45d 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_ge_link_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_ge_link_bulb.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_hanssem_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_hanssem_switch.lua index 37de10bdec..96f4581310 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_hanssem_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_hanssem_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli-vzm31-sn.lua b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli-vzm31-sn.lua deleted file mode 100755 index 41ae8a049b..0000000000 --- a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli-vzm31-sn.lua +++ /dev/null @@ -1,484 +0,0 @@ --- Copyright 2025 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 clusters = require "st.zigbee.zcl.clusters" -local cluster_base = require "st.zigbee.cluster_base" -local BasicCluster = clusters.Basic -local OnOffCluster = clusters.OnOff -local LevelCluster = clusters.Level -local SimpleMeteringCluster = clusters.SimpleMetering -local ElectricalMeasurementCluster = clusters.ElectricalMeasurement -local capabilities = require "st.capabilities" -local t_utils = require "integration_test.utils" -local zigbee_test_utils = require "integration_test.zigbee_test_utils" -local data_types = require "st.zigbee.data_types" -local utils = require "st.utils" - -local PRIVATE_CLUSTER_ID = 0xFC31 -local PRIVATE_CMD_NOTIF_ID = 0x01 -local PRIVATE_CMD_SCENE_ID =0x00 -local MFG_CODE = 0x122F - -local parent_profile = t_utils.get_profile_definition("inovelli-vzm31-sn.yml") -local child_profile = t_utils.get_profile_definition("rgbw-bulb-2700K-6500K.yml") - -local mock_device = test.mock_device.build_test_zigbee_device({ - label = "Inovelli 2-in-1 Blue Series", - profile = parent_profile, - zigbee_endpoints = { - [1] = { - id = 1, - manufacturer = "Inovelli", - model = "VZM31-SN", - server_clusters = { 0x0000, 0x0006, 0x0008, 0x0702, 0x0B04 }, - }, - [2] = { - id = 2, - manufacturer = "Inovelli", - model = "VZM31-SN", - server_clusters = { 0x0006 }, - }, - [3] = { - id = 3, - manufacturer = "Inovelli", - model = "VZM31-SN", - server_clusters = { 0x0006 }, - }, - [4] = { - id = 4, - manufacturer = "Inovelli", - model = "VZM31-SN", - server_clusters = { 0x0006 }, - }, - }, - fingerprinted_endpoint_id = 0x01 -}) - -local mock_first_child = test.mock_device.build_test_child_device({ - profile = child_profile, - device_network_id = string.format("%04X:%02X", mock_device:get_short_address(), 2), - parent_device_id = mock_device.id, - parent_assigned_child_key = string.format("%02X", 2) -}) - -zigbee_test_utils.prepare_zigbee_env_info() - -local function test_init() - mock_device:set_field("_configuration_version", 1, {persist = true}) - test.mock_device.add_test_device(mock_device) - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(0))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.powerMeter.power(0))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.energyMeter.energy(0))) - test.socket.capability:__expect_send(mock_device:generate_test_message("button1", capabilities.button.supportedButtonValues({"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, { visibility = { displayed = false } }))) - test.socket.capability:__expect_send(mock_device:generate_test_message("button2", capabilities.button.supportedButtonValues({"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, { visibility = { displayed = false } }))) - test.socket.capability:__expect_send(mock_device:generate_test_message("button3", capabilities.button.supportedButtonValues({"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, { visibility = { displayed = false } }))) - test.socket.capability:__expect_send(mock_device:generate_test_message("button1", capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }))) - test.socket.capability:__expect_send(mock_device:generate_test_message("button2", capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }))) - test.socket.capability:__expect_send(mock_device:generate_test_message("button3", capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }))) - - test.socket.zigbee:__expect_send({mock_device.id, BasicCluster.attributes.SWBuildID:read(mock_device)}) - - test.mock_device.add_test_device(mock_first_child) - test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.colorControl.hue(1))) - test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.colorControl.saturation(1))) - test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.colorTemperature.colorTemperature(6500))) - test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.switchLevel.level(100))) - test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.switch.switch.off())) - -end - -test.set_test_init_function(test_init) - -test.register_coroutine_test( - "lifecycle configure event should configure device", - function () - test.socket.zigbee:__set_channel_ordering("relaxed") - test.socket.device_lifecycle:__queue_receive({mock_device.id, "doConfigure"}) - test.socket.zigbee:__expect_send({ - mock_device.id, - LevelCluster.attributes.CurrentLevel:read(mock_device) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - SimpleMeteringCluster.attributes.InstantaneousDemand:read(mock_device) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - SimpleMeteringCluster.attributes.CurrentSummationDelivered:read(mock_device) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - SimpleMeteringCluster.attributes.Multiplier:read(mock_device) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - SimpleMeteringCluster.attributes.Divisor:read(mock_device) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - ElectricalMeasurementCluster.attributes.ActivePower:read(mock_device) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - ElectricalMeasurementCluster.attributes.ACPowerMultiplier:read(mock_device) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - ElectricalMeasurementCluster.attributes.ACPowerDivisor:read(mock_device) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - OnOffCluster.attributes.OnOff:read(mock_device) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - OnOffCluster.attributes.OnOff:read(mock_device):to_endpoint(0x02) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - OnOffCluster.attributes.OnOff:read(mock_device):to_endpoint(0x03) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - OnOffCluster.attributes.OnOff:read(mock_device):to_endpoint(0x04) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - OnOffCluster.attributes.OnOff:configure_reporting(mock_device, 0, 300):to_endpoint(1) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - OnOffCluster.attributes.OnOff:configure_reporting(mock_device, 0, 300):to_endpoint(2) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - OnOffCluster.attributes.OnOff:configure_reporting(mock_device, 0, 300):to_endpoint(3) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - OnOffCluster.attributes.OnOff:configure_reporting(mock_device, 0, 300):to_endpoint(4) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - LevelCluster.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - SimpleMeteringCluster.attributes.InstantaneousDemand:configure_reporting(mock_device, 5, 3600, 5) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - SimpleMeteringCluster.attributes.CurrentSummationDelivered:configure_reporting(mock_device, 5, 3600, 1) - }) - - test.socket.zigbee:__expect_send({ - mock_device.id, - ElectricalMeasurementCluster.attributes.ACPowerMultiplier:configure_reporting(mock_device, 1, 43200, 1) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - ElectricalMeasurementCluster.attributes.ACPowerDivisor:configure_reporting(mock_device, 1, 43200, 1) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - ElectricalMeasurementCluster.attributes.ActivePower:configure_reporting(mock_device, 5, 3600, 5) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - BasicCluster.attributes.SWBuildID:read(mock_device) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - ElectricalMeasurementCluster.ID) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - SimpleMeteringCluster.ID) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - LevelCluster.ID) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - OnOffCluster.ID, 1):to_endpoint(1) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - OnOffCluster.ID, 2):to_endpoint(2) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - OnOffCluster.ID, 3):to_endpoint(3) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - OnOffCluster.ID, 4):to_endpoint(4) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - PRIVATE_CLUSTER_ID, 2) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 21, MFG_CODE) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - ElectricalMeasurementCluster.attributes.ACPowerDivisor:read(mock_device) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - ElectricalMeasurementCluster.attributes.ACPowerMultiplier:read(mock_device) - }) - - mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end -) - -test.register_coroutine_test( - "parameter258 in infochanged", - function() - test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ - preferences = { ["parameter258"] = "0" } - })) - test.mock_time.advance_time(3) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 258, - MFG_CODE, data_types.Boolean, false) }) - test.socket.zigbee:__expect_send({ mock_device.id, - BasicCluster.attributes.SWBuildID:read(mock_device) }) - end -) - -test.register_coroutine_test( - "parameter22 in infochanged", - function() - test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ - preferences = { ["parameter22"] = "0" } - })) - test.mock_time.advance_time(3) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 22, - MFG_CODE, data_types.Uint8, 0) }) - test.socket.zigbee:__expect_send({ mock_device.id, - BasicCluster.attributes.SWBuildID:read(mock_device) }) - end -) - - -test.register_message_test( - "Capability on command switch on should be handled : parent device", - { - { - channel = "capability", - direction = "receive", - message = { mock_device.id, { capability = "switch", component = "main", command = "on", args = { } } } - }, - { - channel = "zigbee", - direction = "send", - message = { mock_device.id, OnOffCluster.server.commands.On(mock_device) } - } - } -) - -test.register_coroutine_test( - "Capability on command switch on should be handled : child device", - function() - test.socket.capability:__queue_receive({mock_first_child.id, { capability = "switch", component = "main", command = "on", args = {}}}) - test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.switch.switch.on())) - test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") - test.wait_for_events() - test.mock_time.advance_time(60 * 1) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.build_manufacturer_specific_command(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_CMD_NOTIF_ID, MFG_CODE, utils.serialize_int(16803071,4,false,false)) }) - end -) - -test.register_message_test( - "Capability off command switch on should be handled : parent device", - { - { - channel = "capability", - direction = "receive", - message = { mock_device.id, { capability = "switch", component = "main", command = "off", args = { } } } - }, - { - channel = "zigbee", - direction = "send", - message = { mock_device.id, OnOffCluster.server.commands.Off(mock_device) } - } - } -) - -test.register_coroutine_test( - "Capability on command switch on should be handled : child device", - function() - test.socket.capability:__queue_receive({mock_first_child.id, { capability = "switch", component = "main", command = "off", args = {}}}) - test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.switch.switch.off())) - test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") - test.wait_for_events() - test.mock_time.advance_time(60 * 1) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.build_manufacturer_specific_command(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_CMD_NOTIF_ID, MFG_CODE, utils.serialize_int(0,4,false,false)) }) - end -) - - -test.register_message_test( - "Capability setLevel command switch on should be handled : parent device", - { - { - channel = "capability", - direction = "receive", - message = { mock_device.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 57, 0 } } } - }, - { - channel = "zigbee", - direction = "send", - message = { mock_device.id, LevelCluster.server.commands.MoveToLevelWithOnOff(mock_device, - math.floor(57 * 0xFE / 100), - 0) } - } - } -) - -test.register_coroutine_test( - "Capability setLevel command switch on should be handled : child device", - function() - test.socket.capability:__queue_receive({mock_first_child.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 57, 0 }}}) - test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.switchLevel.level(57))) - test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.switch.switch.on())) - test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") - test.wait_for_events() - test.mock_time.advance_time(60 * 1) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.build_manufacturer_specific_command(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_CMD_NOTIF_ID, MFG_CODE, utils.serialize_int(16792063,4,false,false)) }) - end -) - - -test.register_coroutine_test( - "Capability setColorTemperature command switch on should be handled : child device", - function() - test.socket.capability:__queue_receive({mock_first_child.id, { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = { 1800 }}}) - test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.colorControl.hue(100))) - test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800))) - test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") - test.wait_for_events() - test.mock_time.advance_time(60 * 1) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.build_manufacturer_specific_command(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_CMD_NOTIF_ID, MFG_CODE, utils.serialize_int(33514751,4,false,false)) }) - end -) - -test.register_coroutine_test( - "Capability setColor command switch on should be handled : child device", - function() - test.socket.capability:__queue_receive({mock_first_child.id, { capability = "colorControl", component = "main", command = "setColor", args = { { hue = 50, saturation = 50 } }}}) - test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.colorControl.hue(50))) - test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.colorControl.saturation(50))) - test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") - test.wait_for_events() - test.mock_time.advance_time(60 * 1) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.build_manufacturer_specific_command(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_CMD_NOTIF_ID, MFG_CODE, utils.serialize_int(25191679,4,false,false)) }) - end -) - -local ENDPOINT = 0x01 -local FRAME_CTRL = 0x1D -local PROFILE_ID = 0x0104 - -local build_scene_message = function(device, payload) - local message = zigbee_test_utils.build_custom_command_id( - device, - PRIVATE_CLUSTER_ID, - PRIVATE_CMD_SCENE_ID, - MFG_CODE, - payload, - ENDPOINT - ) - - message.body.zcl_header.frame_ctrl.value = FRAME_CTRL - message.address_header.profile.value = PROFILE_ID - - return message -end - -test.register_coroutine_test( - "Reported private cluster should be handled", - function() - test.socket.zigbee:__queue_receive({ - mock_device.id, - build_scene_message(mock_device, "\x01\x01") - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("button1", capabilities.button.button.held({state_change = true}))) - end -) - -test.register_coroutine_test( - "Handle Power meter", - function() - test.socket.zigbee:__queue_receive({ - mock_device.id, - SimpleMeteringCluster.attributes.InstantaneousDemand:build_test_attr_report(mock_device, 60) - }) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 6.0, unit = "W" })) - ) - - test.socket.zigbee:__queue_receive({ - mock_device.id, - ElectricalMeasurementCluster.attributes.ActivePower:build_test_attr_report(mock_device, 100) - }) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 10.0, unit = "W" })) - ) - end -) - -test.register_coroutine_test( - "Handle Energy meter", - function() - test.socket.zigbee:__queue_receive({ - mock_device.id, - SimpleMeteringCluster.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 600) - }) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 6.0, unit = "kWh" })) - ) - end -) - -test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn.lua b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn.lua new file mode 100644 index 0000000000..79e7a66b54 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn.lua @@ -0,0 +1,537 @@ +-- Copyright 2025 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 capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local cluster_base = require "st.zigbee.cluster_base" +local utils = require "st.utils" +local OTAUpgrade = require("st.zigbee.zcl.clusters").OTAUpgrade +local device_management = require "st.zigbee.device_management" +local zigbee_constants = require "st.zigbee.constants" + +local OnOff = clusters.OnOff +local Level = clusters.Level +local TemperatureMeasurement = clusters.TemperatureMeasurement +local RelativeHumidity = clusters.RelativeHumidity + +-- Inovelli VZM30-SN device identifiers +local INOVELLI_MANUFACTURER_ID = "Inovelli" +local INOVELLI_VZM30_SN_MODEL = "VZM30-SN" + +-- Device endpoints with supported clusters +local inovelli_vzm30_sn_endpoints = { + [1] = { + id = 1, + manufacturer = INOVELLI_MANUFACTURER_ID, + model = INOVELLI_VZM30_SN_MODEL, + server_clusters = {0x0006, 0x0008, 0x0300, 0x0402, 0x0405} -- OnOff, Level, ColorControl, TemperatureMeasurement, RelativeHumidity + } +} + +local mock_inovelli_vzm30_sn = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("inovelli-vzm30-sn.yml"), + zigbee_endpoints = inovelli_vzm30_sn_endpoints, + fingerprinted_endpoint_id = 0x01 +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_inovelli_vzm30_sn) +end +test.set_test_init_function(test_init) + +local supported_button_values = { + ["button1"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button2"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button3"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"} + } + +-- Test device initialization +test.register_message_test( + "Device should initialize properly on added lifecycle event", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_inovelli_vzm30_sn.id, "added" }, + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm30_sn.id, + Level.attributes.CurrentLevel:read(mock_inovelli_vzm30_sn) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm30_sn.id, + OnOff.attributes.OnOff:read(mock_inovelli_vzm30_sn) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm30_sn.id, + TemperatureMeasurement.attributes.MeasuredValue:read(mock_inovelli_vzm30_sn) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm30_sn.id, + RelativeHumidity.attributes.MeasuredValue:read(mock_inovelli_vzm30_sn) + } + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test refresh capability +test.register_message_test( + "Refresh capability should send read commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzm30_sn.id, + { capability = "refresh", command = "refresh", args = {} } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_inovelli_vzm30_sn.id, OnOff.attributes.OnOff:read(mock_inovelli_vzm30_sn) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_inovelli_vzm30_sn.id, Level.attributes.CurrentLevel:read(mock_inovelli_vzm30_sn) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_inovelli_vzm30_sn.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_inovelli_vzm30_sn) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_inovelli_vzm30_sn.id, RelativeHumidity.attributes.MeasuredValue:read(mock_inovelli_vzm30_sn) } + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test switch on command +test.register_message_test( + "Switch on command should send OnOff On command", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzm30_sn.id, + { capability = "switch", command = "on", args = {} } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_inovelli_vzm30_sn.id, clusters.OnOff.server.commands.On(mock_inovelli_vzm30_sn) } + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test switch off command +test.register_message_test( + "Switch off command should send OnOff Off command", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzm30_sn.id, + { capability = "switch", command = "off", args = {} } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_inovelli_vzm30_sn.id, clusters.OnOff.server.commands.Off(mock_inovelli_vzm30_sn) } + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test switch level command +test.register_message_test( + "Switch level command should send Level MoveToLevelWithOnOff command", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzm30_sn.id, + { capability = "switchLevel", command = "setLevel", args = { 50 } } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm30_sn.id, + clusters.Level.server.commands.MoveToLevelWithOnOff(mock_inovelli_vzm30_sn, math.floor(50/100.0 * 254), 0xFFFF) + } + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Build test message for Inovelli private cluster button press +local function build_inovelli_button_message(device, button_number, key_attribute) + local messages = require "st.zigbee.messages" + local zcl_messages = require "st.zigbee.zcl" + local zb_const = require "st.zigbee.constants" + local data_types = require "st.zigbee.data_types" + local frameCtrl = require "st.zigbee.zcl.frame_ctrl" + + -- Combine button_number and key_attribute into a single value + -- button_number in lower byte, key_attribute in upper byte + local combined_value = (key_attribute * 256) + button_number + + -- Create the command body using serialize_int + local command_body = zcl_messages.ZclMessageBody({ + zcl_header = zcl_messages.ZclHeader({ + frame_ctrl = frameCtrl(0x15), -- Manufacturer specific, client to server + mfg_code = data_types.Uint16(0x122F), -- Inovelli manufacturer code + seqno = data_types.Uint8(0x6D), + cmd = data_types.ZCLCommandId(0x00) -- Scene command + }), + zcl_body = data_types.Uint16(combined_value) + }) + + local addrh = messages.AddressHeader( + device:get_short_address(), + 0x02, -- src_endpoint from real device log + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + zb_const.HA_PROFILE_ID, + 0xFC31 -- PRIVATE_CLUSTER_ID + ) + + return messages.ZigbeeMessageRx({ + address_header = addrh, + body = command_body + }) +end + +-- Test button1 pushed +test.register_message_test( + "Button1 pushed should emit button event and update supportedButtonValues", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_inovelli_vzm30_sn.id, build_inovelli_button_message(mock_inovelli_vzm30_sn, 0x01, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm30_sn:generate_test_message( + "button1", + capabilities.button.supportedButtonValues( + supported_button_values["button1"], + { visibility = { displayed = false } } + ) + ) + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm30_sn:generate_test_message("button1", capabilities.button.button.pushed({ state_change = true })) + } + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test button2 pressed 4 times +test.register_message_test( + "Button2 pressed 4 times should emit button event and update supportedButtonValues", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_inovelli_vzm30_sn.id, build_inovelli_button_message(mock_inovelli_vzm30_sn, 0x02, 0x05) } + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm30_sn:generate_test_message( + "button2", + capabilities.button.supportedButtonValues( + supported_button_values["button2"], + { visibility = { displayed = false } } + ) + ) + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm30_sn:generate_test_message("button2", capabilities.button.button.pushed_4x({ state_change = true })) + } + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test consecutive button events - supportedButtonValues should only be sent on first event +test.register_coroutine_test( + "Consecutive button events should only send supportedButtonValues on first event", + function() + test.socket.capability:__set_channel_ordering("relaxed") + + -- First button event: button1 pushed - should send supportedButtonValues + button event + test.socket.zigbee:__queue_receive({ + mock_inovelli_vzm30_sn.id, + build_inovelli_button_message(mock_inovelli_vzm30_sn, 0x01, 0x00) + }) + test.socket.capability:__expect_send( + mock_inovelli_vzm30_sn:generate_test_message( + "button1", + capabilities.button.supportedButtonValues( + supported_button_values["button1"], + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_inovelli_vzm30_sn:generate_test_message("button1", capabilities.button.button.pushed({ state_change = true })) + ) + + -- Second button event: button1 pushed_2x - should only send button event, NOT supportedButtonValues + test.socket.zigbee:__queue_receive({ + mock_inovelli_vzm30_sn.id, + build_inovelli_button_message(mock_inovelli_vzm30_sn, 0x01, 0x03) + }) + test.socket.capability:__expect_send( + mock_inovelli_vzm30_sn:generate_test_message("button1", capabilities.button.button.pushed_2x({ state_change = true })) + ) + end +) + +-- Test temperature measurement +test.register_message_test( + "Temperature measurement should emit temperature events", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_inovelli_vzm30_sn.id, + TemperatureMeasurement.attributes.MeasuredValue:build_test_attr_report(mock_inovelli_vzm30_sn, 2500) + } + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm30_sn:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 25.0, unit = "C"})) + } + } +) + +-- Test humidity measurement +test.register_message_test( + "Humidity measurement should emit humidity events", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_inovelli_vzm30_sn.id, + RelativeHumidity.attributes.MeasuredValue:build_test_attr_report(mock_inovelli_vzm30_sn, 6500) + } + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm30_sn:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity(65)) + } + } +) + +-- Test power meter from ElectricalMeasurement +test.register_coroutine_test( + "Power meter from ElectricalMeasurement should emit power events", + function() + -- Set the divisor field (default handlers use 10 if not set, but we can set it for consistency) + -- The default handler will use 10 if ELECTRICAL_MEASUREMENT_DIVISOR_KEY is not set + -- Since the test expects 2000 -> 200.0 W, that means divisor of 10 is being used + mock_inovelli_vzm30_sn:set_field(zigbee_constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, 10, {persist = true}) + + test.socket.zigbee:__queue_receive({ + mock_inovelli_vzm30_sn.id, + clusters.ElectricalMeasurement.attributes.ActivePower:build_test_attr_report(mock_inovelli_vzm30_sn, 2000) + }) + test.socket.capability:__expect_send( + mock_inovelli_vzm30_sn:generate_test_message("main", capabilities.powerMeter.power({value = 200.0, unit = "W"})) + ) + end +) + +-- Test energy meter +test.register_coroutine_test( + "Energy meter should emit energy events", + function() + -- Set the divisor field as the device does during configuration + -- For VZM30-SN, the divisor is set to 1000 (like VZM32-SN) + mock_inovelli_vzm30_sn:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, 1000, {persist = true}) + + test.socket.zigbee:__queue_receive({ + mock_inovelli_vzm30_sn.id, + clusters.SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_inovelli_vzm30_sn, 212) + }) + test.socket.capability:__expect_send( + mock_inovelli_vzm30_sn:generate_test_message("main", capabilities.energyMeter.energy({value = 0.212, unit = "kWh"})) + ) + end +) + +-- Test energy meter reset command +test.register_message_test( + "Energy meter reset command should send reset commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzm30_sn.id, + { capability = "energyMeter", command = "resetEnergyMeter", args = {} } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm30_sn.id, + cluster_base.build_manufacturer_specific_command( + mock_inovelli_vzm30_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x02, -- PRIVATE_CMD_ENERGY_RESET_ID + 0x122F, -- MFG_CODE + utils.serialize_int(0, 1, false, false) + ) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm30_sn.id, + clusters.SimpleMetering.attributes.CurrentSummationDelivered:read(mock_inovelli_vzm30_sn) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm30_sn.id, + clusters.ElectricalMeasurement.attributes.ActivePower:read(mock_inovelli_vzm30_sn) + } + } + }, + { + inner_block_ordering = "relaxed" + } +) + + +test.register_coroutine_test( + "doConfigure runs base + VZM30 extras", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_inovelli_vzm30_sn.id, "doConfigure" }) + + -- Button capability messages from base_device_configure + for _, component in pairs(mock_inovelli_vzm30_sn.profile.components) do + if component.id ~= "main" then + test.socket.capability:__expect_send( + mock_inovelli_vzm30_sn:generate_test_message( + component.id, + capabilities.button.supportedButtonValues( + supported_button_values[component.id], + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_inovelli_vzm30_sn:generate_test_message( + component.id, + capabilities.button.numberOfButtons({value = 1}, { visibility = { displayed = false } }) + ) + ) + end + end + + -- device:configure() sends bind requests and configure reporting (default handler) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, require("integration_test.zigbee_test_utils").build_bind_request(mock_inovelli_vzm30_sn, require("integration_test.zigbee_test_utils").mock_hub_eui, clusters.Level.ID) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, clusters.Level.attributes.CurrentLevel:configure_reporting(mock_inovelli_vzm30_sn, 1, 3600, 1) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, device_management.build_bind_request(mock_inovelli_vzm30_sn, RelativeHumidity.ID, require("integration_test.zigbee_test_utils").mock_hub_eui) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, RelativeHumidity.attributes.MeasuredValue:configure_reporting(mock_inovelli_vzm30_sn, 30, 3600, 100) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, require("integration_test.zigbee_test_utils").build_bind_request(mock_inovelli_vzm30_sn, require("integration_test.zigbee_test_utils").mock_hub_eui, clusters.OnOff.ID) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, clusters.OnOff.attributes.OnOff:configure_reporting(mock_inovelli_vzm30_sn, 0, 300) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, device_management.build_bind_request(mock_inovelli_vzm30_sn, TemperatureMeasurement.ID, require("integration_test.zigbee_test_utils").mock_hub_eui) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_inovelli_vzm30_sn, 30, 600, 100) }) + + -- base_device_configure sends OTA ImageNotify and private cluster bind + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, OTAUpgrade.commands.ImageNotify(mock_inovelli_vzm30_sn, 0x00, 100, 0x122F, 0xFFFF, 0xFFFFFFFF) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, device_management.build_bind_request(mock_inovelli_vzm30_sn, 0xFC31, require("integration_test.zigbee_test_utils").mock_hub_eui, 2) }) + + -- Read divisors/multipliers + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, clusters.SimpleMetering.attributes.Multiplier:read(mock_inovelli_vzm30_sn) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, clusters.ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_inovelli_vzm30_sn) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, clusters.ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_inovelli_vzm30_sn) }) + + -- VZM30-specific: temperature and humidity reporting configuration + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_inovelli_vzm30_sn, 30, 3600, 50) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm30_sn.id, RelativeHumidity.attributes.MeasuredValue:configure_reporting(mock_inovelli_vzm30_sn, 30, 3600, 50) }) + + mock_inovelli_vzm30_sn:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn_child.lua b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn_child.lua new file mode 100644 index 0000000000..e40ead721a --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn_child.lua @@ -0,0 +1,356 @@ +-- Copyright 2025 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 capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local cluster_base = require "st.zigbee.cluster_base" +local utils = require "st.utils" + +-- Device endpoints with supported clusters +local inovelli_vzm30_sn_endpoints = { + [1] = { + id = 1, + manufacturer = "Inovelli", + model = "VZM30-SN", + server_clusters = {0x0006, 0x0008, 0x0300} -- OnOff, Level, ColorControl + } +} + +local mock_parent_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("inovelli-vzm30-sn.yml"), + zigbee_endpoints = inovelli_vzm30_sn_endpoints, + fingerprinted_endpoint_id = 0x01 +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local mock_child_device = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("rgbw-bulb.yml"), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = "notification" +}) + +local function test_init() + test.mock_device.add_test_device(mock_parent_device) + test.mock_device.add_test_device(mock_child_device) +end +test.set_test_init_function(test_init) + +-- Test child device initialization +test.register_message_test( + "Child device should initialize with default color values", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_child_device.id, "added" }, + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorControl.hue(1)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorControl.saturation(1)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = 2700, maximum = 6500} })) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(6500)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.switchLevel.level(100)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.switch.switch("off")) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test child device switch on command +test.register_coroutine_test( + "Child device switch on should emit events and send configuration to parent", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local level = 100 -- Default level for child devices + local color = 100 -- Default color for child devices (since device starts with no hue state) + local effect = 1 -- Default notificationType + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switch", command = "on", args = {} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(notificationValue, 4, false, false) + ) + }) + end +) + +-- Test child device switch off command +test.register_coroutine_test( + "Child device switch off should emit events and send configuration to parent", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switch", command = "off", args = {} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("off")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(0, 4, false, false) + ) + }) + end +) + +-- Test child device level command +test.register_coroutine_test( + "Child device level command should emit events and send configuration to parent", + function() + local level = math.random(1, 99) + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local effect = 1 -- Default notificationType + local color = 100 -- Default color for child devices (since device starts with no hue state) + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) -- Use the actual level from command + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switchLevel", command = "setLevel", args = { level } } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switchLevel.level(level)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(notificationValue, 4, false, false) + ) + }) + end +) + +-- Test child device color command +test.register_coroutine_test( + "Child device color command should emit events and send configuration to parent", + function() + local color = math.random(0, 100) + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local level = 100 -- Default level for child devices + local effect = 1 -- Default notificationType + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "colorControl", command = "setColor", args = {{ hue = color, saturation = 100 }} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.hue(color)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.saturation(100)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(notificationValue, 4, false, false) + ) + }) + end +) + +-- Test child device color temperature command +test.register_coroutine_test( + "Child device color temperature command should emit events and send configuration to parent", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local level = 100 -- Default level for child devices + local color = 100 -- Default color for child devices (since device starts with no hue state) + local effect = 1 -- Default notificationType + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "colorTemperature", command = "setColorTemperature", args = { 3000 } } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.hue(100)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(3000)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(notificationValue, 4, false, false) + ) + }) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn_preferences.lua b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn_preferences.lua new file mode 100644 index 0000000000..0726a2d575 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm30_sn_preferences.lua @@ -0,0 +1,210 @@ +-- Copyright 2025 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 zigbee_test_utils = require "integration_test.zigbee_test_utils" +local data_types = require "st.zigbee.data_types" +local cluster_base = require "st.zigbee.cluster_base" +local utils = require "st.utils" + +-- Device endpoints with supported clusters +local inovelli_vzm30_sn_endpoints = { + [1] = { + id = 1, + manufacturer = "Inovelli", + model = "VZM30-SN", + server_clusters = {0x0006, 0x0008} -- OnOff, Level + } +} + +local mock_inovelli_vzm30_sn = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("inovelli-vzm30-sn.yml"), + zigbee_endpoints = inovelli_vzm30_sn_endpoints, + fingerprinted_endpoint_id = 0x01, + label = "Inovelli VZM30-SN" +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_inovelli_vzm30_sn) +end +test.set_test_init_function(test_init) + +-- Test parameter1 preference change +test.register_coroutine_test( + "parameter1 preference should send configuration command", + function() + local new_param_value = 50 + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm30_sn:generate_info_changed({preferences = {parameter1 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm30_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm30_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 1, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Uint8, + new_param_value + ) + }) + end +) + +-- Test parameter9 preference change +test.register_coroutine_test( + "parameter15 preference should send configuration command", + function() + local new_param_value = 10 + local expected_value = utils.round(new_param_value / 100 * 254) + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm30_sn:generate_info_changed({preferences = {parameter15 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm30_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm30_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 15, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Uint8, + expected_value + ) + }) + end +) + +-- Test parameter52 preference change +test.register_coroutine_test( + "parameter52 preference should send configuration command", + function() + local new_param_value = true + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm30_sn:generate_info_changed({preferences = {parameter52 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm30_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm30_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 52, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Boolean, + new_param_value + ) + }) + end +) + +-- Test parameter258 preference change +test.register_coroutine_test( + "parameter258 preference should send configuration command", + function() + local new_param_value = false + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm30_sn:generate_info_changed({preferences = {parameter258 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm30_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm30_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 258, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Boolean, + new_param_value + ) + }) + end +) + +-- Test parameter11 preference change (VZM30-only, same as VZM31) +test.register_coroutine_test( + "parameter11 preference should send configuration command", + function() + local new_param_value = true + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm30_sn:generate_info_changed({preferences = {parameter11 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm30_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm30_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 11, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Boolean, + new_param_value + ) + }) + end +) + +-- Test parameter17 preference change (VZM30-only, same as VZM31) +test.register_coroutine_test( + "parameter95 preference should send configuration command", + function() + local new_param_value = 64 + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm30_sn:generate_info_changed({preferences = {parameter95 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm30_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm30_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 95, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Uint8, + new_param_value + ) + }) + end +) + +-- Test parameter22 preference change (VZM30-only, same as VZM31) +test.register_coroutine_test( + "parameter22 preference should send configuration command", + function() + local new_param_value = 2 + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm30_sn:generate_info_changed({preferences = {parameter22 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm30_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm30_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 22, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Uint8, + new_param_value + ) + }) + end +) + +-- Test notificationChild preference change +test.register_coroutine_test( + "notificationChild preference should create child device when enabled", + function() + mock_inovelli_vzm30_sn:expect_device_create({ + type = "EDGE_CHILD", + label = "Inovelli VZM30-SN Notification", + profile = "rgbw-bulb-2700K-6500K", + parent_device_id = mock_inovelli_vzm30_sn.id, + parent_assigned_child_key = "notification" + }) + + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm30_sn:generate_info_changed({preferences = {notificationChild = true}})) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn.lua b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn.lua new file mode 100644 index 0000000000..a3d57415b1 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn.lua @@ -0,0 +1,421 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local cluster_base = require "st.zigbee.cluster_base" +local utils = require "st.utils" +local OTAUpgrade = require("st.zigbee.zcl.clusters").OTAUpgrade +local device_management = require "st.zigbee.device_management" +local zigbee_constants = require "st.zigbee.constants" + +-- Inovelli VZM31-SN device identifiers +local INOVELLI_MANUFACTURER_ID = "Inovelli" +local INOVELLI_VZM31_SN_MODEL = "VZM31-SN" + +-- Device endpoints with supported clusters +local inovelli_vzm31_sn_endpoints = { + [1] = { + id = 1, + manufacturer = INOVELLI_MANUFACTURER_ID, + model = INOVELLI_VZM31_SN_MODEL, + server_clusters = {0x0006, 0x0008, 0x0300} -- OnOff, Level, ColorControl + } +} + +local mock_inovelli_vzm31_sn = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("inovelli-vzm31-sn.yml"), + zigbee_endpoints = inovelli_vzm31_sn_endpoints, + fingerprinted_endpoint_id = 0x01 +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_inovelli_vzm31_sn) +end +test.set_test_init_function(test_init) + +local supported_button_values = { + ["button1"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button2"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button3"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"} +} + +-- Test device initialization +test.register_message_test( + "Device should initialize properly on added lifecycle event", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_inovelli_vzm31_sn.id, "added" }, + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm31_sn.id, + clusters.Level.attributes.CurrentLevel:read(mock_inovelli_vzm31_sn) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm31_sn.id, + clusters.OnOff.attributes.OnOff:read(mock_inovelli_vzm31_sn) + } + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test switch on command +test.register_message_test( + "Switch on command should send OnOff On command", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzm31_sn.id, + { capability = "switch", command = "on", args = {} } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_inovelli_vzm31_sn.id, clusters.OnOff.server.commands.On(mock_inovelli_vzm31_sn) } + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test switch off command +test.register_message_test( + "Switch off command should send OnOff Off command", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzm31_sn.id, + { capability = "switch", command = "off", args = {} } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_inovelli_vzm31_sn.id, clusters.OnOff.server.commands.Off(mock_inovelli_vzm31_sn) } + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test switch level command +test.register_message_test( + "Switch level command should send Level MoveToLevelWithOnOff command", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzm31_sn.id, + { capability = "switchLevel", command = "setLevel", args = { 50 } } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm31_sn.id, + clusters.Level.server.commands.MoveToLevelWithOnOff(mock_inovelli_vzm31_sn, math.floor(50/100.0 * 254), 0xFFFF) + } + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Build test message for Inovelli private cluster button press +local function build_inovelli_button_message(device, button_number, key_attribute) + local messages = require "st.zigbee.messages" + local zcl_messages = require "st.zigbee.zcl" + local zb_const = require "st.zigbee.constants" + local data_types = require "st.zigbee.data_types" + local frameCtrl = require "st.zigbee.zcl.frame_ctrl" + + -- Combine button_number and key_attribute into a single value + -- button_number in lower byte, key_attribute in upper byte + local combined_value = (key_attribute * 256) + button_number + + -- Create the command body using serialize_int + local command_body = zcl_messages.ZclMessageBody({ + zcl_header = zcl_messages.ZclHeader({ + frame_ctrl = frameCtrl(0x15), -- Manufacturer specific, client to server + mfg_code = data_types.Uint16(0x122F), -- Inovelli manufacturer code + seqno = data_types.Uint8(0x6D), + cmd = data_types.ZCLCommandId(0x00) -- Scene command + }), + zcl_body = data_types.Uint16(combined_value) + }) + + local addrh = messages.AddressHeader( + device:get_short_address(), + 0x02, -- src_endpoint from real device log + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + zb_const.HA_PROFILE_ID, + 0xFC31 -- PRIVATE_CLUSTER_ID + ) + + return messages.ZigbeeMessageRx({ + address_header = addrh, + body = command_body + }) +end + +-- Test button1 pushed +test.register_message_test( + "Button1 pushed should emit button event and update supportedButtonValues", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_inovelli_vzm31_sn.id, build_inovelli_button_message(mock_inovelli_vzm31_sn, 0x01, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm31_sn:generate_test_message( + "button1", + capabilities.button.supportedButtonValues( + supported_button_values["button1"], + { visibility = { displayed = false } } + ) + ) + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm31_sn:generate_test_message("button1", capabilities.button.button.pushed({ state_change = true })) + } + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test button2 pressed 4 times +test.register_message_test( + "Button2 pressed 4 times should emit button event and update supportedButtonValues", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_inovelli_vzm31_sn.id, build_inovelli_button_message(mock_inovelli_vzm31_sn, 0x02, 0x05) } + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm31_sn:generate_test_message( + "button2", + capabilities.button.supportedButtonValues( + supported_button_values["button2"], + { visibility = { displayed = false } } + ) + ) + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm31_sn:generate_test_message("button2", capabilities.button.button.pushed_4x({ state_change = true })) + } + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test consecutive button events - supportedButtonValues should only be sent on first event +test.register_coroutine_test( + "Consecutive button events should only send supportedButtonValues on first event", + function() + test.socket.capability:__set_channel_ordering("relaxed") + + -- First button event: button1 pushed - should send supportedButtonValues + button event + test.socket.zigbee:__queue_receive({ + mock_inovelli_vzm31_sn.id, + build_inovelli_button_message(mock_inovelli_vzm31_sn, 0x01, 0x00) + }) + test.socket.capability:__expect_send( + mock_inovelli_vzm31_sn:generate_test_message( + "button1", + capabilities.button.supportedButtonValues( + supported_button_values["button1"], + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_inovelli_vzm31_sn:generate_test_message("button1", capabilities.button.button.pushed({ state_change = true })) + ) + + -- Second button event: button1 pushed_2x - should only send button event, NOT supportedButtonValues + test.socket.zigbee:__queue_receive({ + mock_inovelli_vzm31_sn.id, + build_inovelli_button_message(mock_inovelli_vzm31_sn, 0x01, 0x03) + }) + test.socket.capability:__expect_send( + mock_inovelli_vzm31_sn:generate_test_message("button1", capabilities.button.button.pushed_2x({ state_change = true })) + ) + end +) + +-- Test power meter from ElectricalMeasurement +test.register_coroutine_test( + "Power meter from ElectricalMeasurement should emit power events", + function() + -- Set the divisor field (default handlers use 10 if not set, but we can set it for consistency) + -- The default handler will use 10 if ELECTRICAL_MEASUREMENT_DIVISOR_KEY is not set + -- Since the test expects 2000 -> 200.0 W, that means divisor of 10 is being used + mock_inovelli_vzm31_sn:set_field(zigbee_constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, 10, {persist = true}) + + test.socket.zigbee:__queue_receive({ + mock_inovelli_vzm31_sn.id, + clusters.ElectricalMeasurement.attributes.ActivePower:build_test_attr_report(mock_inovelli_vzm31_sn, 2000) + }) + test.socket.capability:__expect_send( + mock_inovelli_vzm31_sn:generate_test_message("main", capabilities.powerMeter.power({value = 200.0, unit = "W"})) + ) + end +) + +-- Test energy meter +test.register_coroutine_test( + "Energy meter should emit energy events", + function() + -- Set the divisor field as the device reads during configuration + -- For VZM31, the divisor is read from the device, but for testing we need to set it + -- The test expects 50000 -> 500.0 kWh, which means divisor of 100 + mock_inovelli_vzm31_sn:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, 100, {persist = true}) + + test.socket.zigbee:__queue_receive({ + mock_inovelli_vzm31_sn.id, + clusters.SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_inovelli_vzm31_sn, 50000) + }) + test.socket.capability:__expect_send( + mock_inovelli_vzm31_sn:generate_test_message("main", capabilities.energyMeter.energy({value = 500.0, unit = "kWh"})) + ) + end +) + +-- Test energy meter reset command +test.register_message_test( + "Energy meter reset command should send reset commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzm31_sn.id, + { capability = "energyMeter", command = "resetEnergyMeter", args = {} } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm31_sn.id, + cluster_base.build_manufacturer_specific_command( + mock_inovelli_vzm31_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x02, -- PRIVATE_CMD_ENERGY_RESET_ID + 0x122F, -- MFG_CODE + utils.serialize_int(0, 1, false, false) + ) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm31_sn.id, + clusters.SimpleMetering.attributes.CurrentSummationDelivered:read(mock_inovelli_vzm31_sn) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm31_sn.id, + clusters.ElectricalMeasurement.attributes.ActivePower:read(mock_inovelli_vzm31_sn) + } + } + }, + { + inner_block_ordering = "relaxed" + } +) + + +test.register_coroutine_test( + "doConfigure runs base config (VZM31)", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_inovelli_vzm31_sn.id, "doConfigure" }) + + -- Button capability messages from base_device_configure + for _, component in pairs(mock_inovelli_vzm31_sn.profile.components) do + if component.id ~= "main" then + test.socket.capability:__expect_send( + mock_inovelli_vzm31_sn:generate_test_message( + component.id, + capabilities.button.supportedButtonValues( + supported_button_values[component.id], + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_inovelli_vzm31_sn:generate_test_message( + component.id, + capabilities.button.numberOfButtons({value = 1}, { visibility = { displayed = false } }) + ) + ) + end + end + + -- device:configure() sends bind requests and configure reporting (default handler) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm31_sn.id, require("integration_test.zigbee_test_utils").build_bind_request(mock_inovelli_vzm31_sn, require("integration_test.zigbee_test_utils").mock_hub_eui, clusters.OnOff.ID) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm31_sn.id, clusters.OnOff.attributes.OnOff:configure_reporting(mock_inovelli_vzm31_sn, 0, 300) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm31_sn.id, require("integration_test.zigbee_test_utils").build_bind_request(mock_inovelli_vzm31_sn, require("integration_test.zigbee_test_utils").mock_hub_eui, clusters.Level.ID) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm31_sn.id, clusters.Level.attributes.CurrentLevel:configure_reporting(mock_inovelli_vzm31_sn, 1, 3600, 1) }) + + -- base_device_configure sends OTA ImageNotify and private cluster bind + test.socket.zigbee:__expect_send({ mock_inovelli_vzm31_sn.id, OTAUpgrade.commands.ImageNotify(mock_inovelli_vzm31_sn, 0x00, 100, 0x122F, 0xFFFF, 0xFFFFFFFF) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm31_sn.id, device_management.build_bind_request(mock_inovelli_vzm31_sn, 0xFC31, require("integration_test.zigbee_test_utils").mock_hub_eui, 2) }) + + -- Read divisors/multipliers + test.socket.zigbee:__expect_send({ mock_inovelli_vzm31_sn.id, clusters.SimpleMetering.attributes.Divisor:read(mock_inovelli_vzm31_sn) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm31_sn.id, clusters.SimpleMetering.attributes.Multiplier:read(mock_inovelli_vzm31_sn) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm31_sn.id, clusters.ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_inovelli_vzm31_sn) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm31_sn.id, clusters.ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_inovelli_vzm31_sn) }) + + mock_inovelli_vzm31_sn:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn_child.lua b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn_child.lua new file mode 100644 index 0000000000..c6476cba5e --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn_child.lua @@ -0,0 +1,346 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local cluster_base = require "st.zigbee.cluster_base" +local utils = require "st.utils" + +-- Device endpoints with supported clusters +local inovelli_vzm31_sn_endpoints = { + [1] = { + id = 1, + manufacturer = "Inovelli", + model = "VZM31-SN", + server_clusters = {0x0006, 0x0008, 0x0300} -- OnOff, Level, ColorControl + } +} + +local mock_parent_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("inovelli-vzm31-sn.yml"), + zigbee_endpoints = inovelli_vzm31_sn_endpoints, + fingerprinted_endpoint_id = 0x01 +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local mock_child_device = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("rgbw-bulb.yml"), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = "notification" +}) + +local function test_init() + test.mock_device.add_test_device(mock_parent_device) + test.mock_device.add_test_device(mock_child_device) +end +test.set_test_init_function(test_init) + +-- Test child device initialization +test.register_message_test( + "Child device should initialize with default color values", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_child_device.id, "added" }, + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorControl.hue(1)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorControl.saturation(1)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = 2700, maximum = 6500} })) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(6500)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.switchLevel.level(100)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.switch.switch("off")) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test child device switch on command +test.register_coroutine_test( + "Child device switch on should emit events and send configuration to parent", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local level = 100 -- Default level for child devices + local color = 100 -- Default color for child devices (since device starts with no hue state) + local effect = 1 -- Default notificationType + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switch", command = "on", args = {} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(notificationValue, 4, false, false) + ) + }) + end +) + +-- Test child device switch off command +test.register_coroutine_test( + "Child device switch off should emit events and send configuration to parent", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switch", command = "off", args = {} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("off")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(0, 4, false, false) + ) + }) + end +) + +-- Test child device level command +test.register_coroutine_test( + "Child device level command should emit events and send configuration to parent", + function() + local level = math.random(1, 99) + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local effect = 1 -- Default notificationType + local color = 100 -- Default color for child devices (since device starts with no hue state) + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) -- Use the actual level from command + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switchLevel", command = "setLevel", args = { level } } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switchLevel.level(level)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(notificationValue, 4, false, false) + ) + }) + end +) + +-- Test child device color command +test.register_coroutine_test( + "Child device color command should emit events and send configuration to parent", + function() + local color = math.random(0, 100) + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local level = 100 -- Default level for child devices + local effect = 1 -- Default notificationType + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "colorControl", command = "setColor", args = {{ hue = color, saturation = 100 }} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.hue(color)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.saturation(100)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(notificationValue, 4, false, false) + ) + }) + end +) + +-- Test child device color temperature command +test.register_coroutine_test( + "Child device color temperature command should emit events and send configuration to parent", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local level = 100 -- Default level for child devices + local color = 100 -- Default color for child devices (since device starts with no hue state) + local effect = 1 -- Default notificationType + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "colorTemperature", command = "setColorTemperature", args = { 3000 } } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.hue(100)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(3000)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(notificationValue, 4, false, false) + ) + }) + end +) + +test.run_registered_tests() + diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn_preferences.lua b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn_preferences.lua new file mode 100644 index 0000000000..64b0d51ee5 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm31_sn_preferences.lua @@ -0,0 +1,200 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local data_types = require "st.zigbee.data_types" +local cluster_base = require "st.zigbee.cluster_base" +local utils = require "st.utils" + +-- Device endpoints with supported clusters +local inovelli_vzm31_sn_endpoints = { + [1] = { + id = 1, + manufacturer = "Inovelli", + model = "VZM31-SN", + server_clusters = {0x0006, 0x0008} -- OnOff, Level + } +} + +local mock_inovelli_vzm31_sn = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("inovelli-vzm31-sn.yml"), + zigbee_endpoints = inovelli_vzm31_sn_endpoints, + fingerprinted_endpoint_id = 0x01, + label = "Inovelli VZM31-SN" +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_inovelli_vzm31_sn) +end +test.set_test_init_function(test_init) + +-- Test parameter1 preference change +test.register_coroutine_test( + "parameter1 preference should send configuration command", + function() + local new_param_value = 50 + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm31_sn:generate_info_changed({preferences = {parameter1 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm31_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm31_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 1, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Uint8, + new_param_value + ) + }) + end +) + +-- Test parameter9 preference change +test.register_coroutine_test( + "parameter9 preference should send configuration command", + function() + local new_param_value = 10 + local expected_value = utils.round(new_param_value / 100 * 254) + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm31_sn:generate_info_changed({preferences = {parameter9 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm31_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm31_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 9, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Uint8, + expected_value + ) + }) + end +) + +-- Test parameter52 preference change +test.register_coroutine_test( + "parameter52 preference should send configuration command", + function() + local new_param_value = true + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm31_sn:generate_info_changed({preferences = {parameter52 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm31_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm31_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 52, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Boolean, + new_param_value + ) + }) + end +) + +-- Test parameter258 preference change +test.register_coroutine_test( + "parameter258 preference should send configuration command", + function() + local new_param_value = false + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm31_sn:generate_info_changed({preferences = {parameter258 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm31_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm31_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 258, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Boolean, + new_param_value + ) + }) + end +) + +-- Test parameter11 preference change (VZM31-only) +test.register_coroutine_test( + "parameter11 preference should send configuration command", + function() + local new_param_value = true + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm31_sn:generate_info_changed({preferences = {parameter11 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm31_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm31_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 11, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Boolean, + new_param_value + ) + }) + end +) + +-- Test parameter17 preference change (VZM31-only) +test.register_coroutine_test( + "parameter17 preference should send configuration command", + function() + local new_param_value = 5 + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm31_sn:generate_info_changed({preferences = {parameter17 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm31_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm31_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 17, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Uint8, + new_param_value + ) + }) + end +) + +-- Test parameter22 preference change (VZM31-only) +test.register_coroutine_test( + "parameter22 preference should send configuration command", + function() + local new_param_value = 2 + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm31_sn:generate_info_changed({preferences = {parameter22 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm31_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm31_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 22, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Uint8, + new_param_value + ) + }) + end +) + +-- Test notificationChild preference change +test.register_coroutine_test( + "notificationChild preference should create child device when enabled", + function() + mock_inovelli_vzm31_sn:expect_device_create({ + type = "EDGE_CHILD", + label = "Inovelli VZM31-SN Notification", + profile = "rgbw-bulb-2700K-6500K", + parent_device_id = mock_inovelli_vzm31_sn.id, + parent_assigned_child_key = "notification" + }) + + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm31_sn:generate_info_changed({preferences = {notificationChild = true}})) + end +) + +test.run_registered_tests() + diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn.lua b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn.lua new file mode 100644 index 0000000000..0a288f27d4 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn.lua @@ -0,0 +1,527 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local cluster_base = require "st.zigbee.cluster_base" +local utils = require "st.utils" +local OTAUpgrade = require("st.zigbee.zcl.clusters").OTAUpgrade +local device_management = require "st.zigbee.device_management" +local zigbee_constants = require "st.zigbee.constants" + +local OnOff = clusters.OnOff +local Level = clusters.Level + +-- Inovelli VZM32-SN device identifiers +local INOVELLI_MANUFACTURER_ID = "Inovelli" +local INOVELLI_VZM32_SN_MODEL = "VZM32-SN" + +-- Device endpoints with supported clusters +local inovelli_vzm32_sn_endpoints = { + [1] = { + id = 1, + manufacturer = INOVELLI_MANUFACTURER_ID, + model = INOVELLI_VZM32_SN_MODEL, + server_clusters = {0x0006, 0x0008, 0x0300, 0x0406} -- OnOff, Level, ColorControl, OccupancySensing + } +} + +local mock_inovelli_vzm32_sn = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("inovelli-vzm32-sn.yml"), + zigbee_endpoints = inovelli_vzm32_sn_endpoints, + fingerprinted_endpoint_id = 0x01 +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_inovelli_vzm32_sn) +end +test.set_test_init_function(test_init) + +local supported_button_values = { + ["button1"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button2"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button3"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"} +} + +-- Test device initialization +test.register_message_test( + "Device should initialize properly on added lifecycle event", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_inovelli_vzm32_sn.id, "added" }, + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm32_sn.id, + clusters.Level.attributes.CurrentLevel:read(mock_inovelli_vzm32_sn) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm32_sn.id, + clusters.OnOff.attributes.OnOff:read(mock_inovelli_vzm32_sn) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm32_sn.id, + clusters.OccupancySensing.attributes.Occupancy:read(mock_inovelli_vzm32_sn) + } + } + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test refresh capability +test.register_message_test( + "Refresh capability should send read commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzm32_sn.id, + { capability = "refresh", command = "refresh", args = {} } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_inovelli_vzm32_sn.id, OnOff.attributes.OnOff:read(mock_inovelli_vzm32_sn) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_inovelli_vzm32_sn.id, Level.attributes.CurrentLevel:read(mock_inovelli_vzm32_sn) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_inovelli_vzm32_sn.id, clusters.OccupancySensing.attributes.Occupancy:read(mock_inovelli_vzm32_sn) } + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test switch on command +test.register_message_test( + "Switch on command should send OnOff On command", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzm32_sn.id, + { capability = "switch", command = "on", args = {} } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_inovelli_vzm32_sn.id, clusters.OnOff.server.commands.On(mock_inovelli_vzm32_sn) } + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test switch off command +test.register_message_test( + "Switch off command should send OnOff Off command", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzm32_sn.id, + { capability = "switch", command = "off", args = {} } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_inovelli_vzm32_sn.id, clusters.OnOff.server.commands.Off(mock_inovelli_vzm32_sn) } + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test switch level command +test.register_message_test( + "Switch level command should send Level MoveToLevelWithOnOff command", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzm32_sn.id, + { capability = "switchLevel", command = "setLevel", args = { 50 } } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm32_sn.id, + clusters.Level.server.commands.MoveToLevelWithOnOff(mock_inovelli_vzm32_sn, math.floor(50/100.0 * 254), 0xFFFF) + } + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Build test message for Inovelli private cluster button press +local function build_inovelli_button_message(device, button_number, key_attribute) + local messages = require "st.zigbee.messages" + local zcl_messages = require "st.zigbee.zcl" + local zb_const = require "st.zigbee.constants" + local data_types = require "st.zigbee.data_types" + local frameCtrl = require "st.zigbee.zcl.frame_ctrl" + + -- Combine button_number and key_attribute into a single value + -- button_number in lower byte, key_attribute in upper byte + local combined_value = (key_attribute * 256) + button_number + + -- Create the command body using serialize_int + local command_body = zcl_messages.ZclMessageBody({ + zcl_header = zcl_messages.ZclHeader({ + frame_ctrl = frameCtrl(0x15), -- Manufacturer specific, client to server + mfg_code = data_types.Uint16(0x122F), -- Inovelli manufacturer code + seqno = data_types.Uint8(0x6D), + cmd = data_types.ZCLCommandId(0x00) -- Scene command + }), + zcl_body = data_types.Uint16(combined_value) + }) + + local addrh = messages.AddressHeader( + device:get_short_address(), + 0x02, -- src_endpoint from real device log + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + zb_const.HA_PROFILE_ID, + 0xFC31 -- PRIVATE_CLUSTER_ID + ) + + return messages.ZigbeeMessageRx({ + address_header = addrh, + body = command_body + }) +end + +-- Test button1 pushed +test.register_message_test( + "Button1 pushed should emit button event and update supportedButtonValues", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_inovelli_vzm32_sn.id, build_inovelli_button_message(mock_inovelli_vzm32_sn, 0x01, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm32_sn:generate_test_message( + "button1", + capabilities.button.supportedButtonValues( + supported_button_values["button1"], + { visibility = { displayed = false } } + ) + ) + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm32_sn:generate_test_message("button1", capabilities.button.button.pushed({ state_change = true })) + } + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test button2 pressed 4 times +test.register_message_test( + "Button2 pressed 4 times should emit button event and update supportedButtonValues", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_inovelli_vzm32_sn.id, build_inovelli_button_message(mock_inovelli_vzm32_sn, 0x02, 0x05) } + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm32_sn:generate_test_message( + "button2", + capabilities.button.supportedButtonValues( + supported_button_values["button2"], + { visibility = { displayed = false } } + ) + ) + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm32_sn:generate_test_message("button2", capabilities.button.button.pushed_4x({ state_change = true })) + } + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test consecutive button events - supportedButtonValues should only be sent on first event +test.register_coroutine_test( + "Consecutive button events should only send supportedButtonValues on first event", + function() + test.socket.capability:__set_channel_ordering("relaxed") + + -- First button event: button1 pushed - should send supportedButtonValues + button event + test.socket.zigbee:__queue_receive({ + mock_inovelli_vzm32_sn.id, + build_inovelli_button_message(mock_inovelli_vzm32_sn, 0x01, 0x00) + }) + test.socket.capability:__expect_send( + mock_inovelli_vzm32_sn:generate_test_message( + "button1", + capabilities.button.supportedButtonValues( + supported_button_values["button1"], + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_inovelli_vzm32_sn:generate_test_message("button1", capabilities.button.button.pushed({ state_change = true })) + ) + + -- Second button event: button1 pushed_2x - should only send button event, NOT supportedButtonValues + test.socket.zigbee:__queue_receive({ + mock_inovelli_vzm32_sn.id, + build_inovelli_button_message(mock_inovelli_vzm32_sn, 0x01, 0x03) + }) + test.socket.capability:__expect_send( + mock_inovelli_vzm32_sn:generate_test_message("button1", capabilities.button.button.pushed_2x({ state_change = true })) + ) + end +) + +-- Test illuminance measurement +test.register_message_test( + "Illuminance measurement should emit illuminance events", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_inovelli_vzm32_sn.id, + clusters.IlluminanceMeasurement.attributes.MeasuredValue:build_test_attr_report(mock_inovelli_vzm32_sn, 11271) + } + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm32_sn:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({value = 13})) + } + } +) + +-- Test motion sensor active +test.register_message_test( + "Motion sensor active should emit motion active event", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_inovelli_vzm32_sn.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_attr_report(mock_inovelli_vzm32_sn, 0x01) + } + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm32_sn:generate_test_message("main", capabilities.motionSensor.motion.active()) + } + } +) + +-- Test motion sensor inactive +test.register_message_test( + "Motion sensor inactive should emit motion inactive event", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_inovelli_vzm32_sn.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_attr_report(mock_inovelli_vzm32_sn, 0x00) + } + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzm32_sn:generate_test_message("main", capabilities.motionSensor.motion.inactive()) + } + } +) + +-- Test power meter from ElectricalMeasurement +test.register_coroutine_test( + "Power meter from ElectricalMeasurement should emit power events", + function() + -- Set the divisor field (default handlers use 10 if not set, but we can set it for consistency) + -- The default handler will use 10 if ELECTRICAL_MEASUREMENT_DIVISOR_KEY is not set + -- Since the test expects 2000 -> 200.0 W, that means divisor of 10 is being used + -- For VZM32, the actual device reads ACPowerDivisor, but default is 10 + mock_inovelli_vzm32_sn:set_field(zigbee_constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, 10, {persist = true}) + + test.socket.zigbee:__queue_receive({ + mock_inovelli_vzm32_sn.id, + clusters.ElectricalMeasurement.attributes.ActivePower:build_test_attr_report(mock_inovelli_vzm32_sn, 2000) + }) + test.socket.capability:__expect_send( + mock_inovelli_vzm32_sn:generate_test_message("main", capabilities.powerMeter.power({value = 200.0, unit = "W"})) + ) + end +) + +-- Test energy meter +test.register_coroutine_test( + "Energy meter should emit energy events", + function() + -- Set the divisor field as the device does during configuration + mock_inovelli_vzm32_sn:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, 1000, {persist = true}) + + test.socket.zigbee:__queue_receive({ + mock_inovelli_vzm32_sn.id, + clusters.SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_inovelli_vzm32_sn, 212) + }) + test.socket.capability:__expect_send( + mock_inovelli_vzm32_sn:generate_test_message("main", capabilities.energyMeter.energy({value = 0.212, unit = "kWh"})) + ) + end +) + +-- Test energy meter reset command +test.register_message_test( + "Energy meter reset command should send reset commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzm32_sn.id, + { capability = "energyMeter", command = "resetEnergyMeter", args = {} } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm32_sn.id, + cluster_base.build_manufacturer_specific_command( + mock_inovelli_vzm32_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x02, -- PRIVATE_CMD_ENERGY_RESET_ID + 0x122F, -- MFG_CODE + utils.serialize_int(0, 1, false, false) + ) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm32_sn.id, + clusters.SimpleMetering.attributes.CurrentSummationDelivered:read(mock_inovelli_vzm32_sn) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_inovelli_vzm32_sn.id, + clusters.ElectricalMeasurement.attributes.ActivePower:read(mock_inovelli_vzm32_sn) + } + } + }, + { + inner_block_ordering = "relaxed" + } +) + + +test.register_coroutine_test( + "doConfigure runs base + VZM32 extras", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_inovelli_vzm32_sn.id, "doConfigure" }) + + -- Button capability messages from base_device_configure + for _, component in pairs(mock_inovelli_vzm32_sn.profile.components) do + if component.id ~= "main" then + test.socket.capability:__expect_send( + mock_inovelli_vzm32_sn:generate_test_message( + component.id, + capabilities.button.supportedButtonValues( + supported_button_values[component.id], + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_inovelli_vzm32_sn:generate_test_message( + component.id, + capabilities.button.numberOfButtons({value = 1}, { visibility = { displayed = false } }) + ) + ) + end + end + + -- device:configure() sends bind requests and configure reporting (default handler) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm32_sn.id, require("integration_test.zigbee_test_utils").build_bind_request(mock_inovelli_vzm32_sn, require("integration_test.zigbee_test_utils").mock_hub_eui, clusters.OnOff.ID) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm32_sn.id, clusters.OnOff.attributes.OnOff:configure_reporting(mock_inovelli_vzm32_sn, 0, 300) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm32_sn.id, require("integration_test.zigbee_test_utils").build_bind_request(mock_inovelli_vzm32_sn, require("integration_test.zigbee_test_utils").mock_hub_eui, clusters.Level.ID) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm32_sn.id, clusters.Level.attributes.CurrentLevel:configure_reporting(mock_inovelli_vzm32_sn, 1, 3600, 1) }) + + -- base_device_configure sends OTA ImageNotify and private cluster bind + test.socket.zigbee:__expect_send({ mock_inovelli_vzm32_sn.id, OTAUpgrade.commands.ImageNotify(mock_inovelli_vzm32_sn, 0x00, 100, 0x122F, 0xFFFF, 0xFFFFFFFF) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm32_sn.id, device_management.build_bind_request(mock_inovelli_vzm32_sn, 0xFC31, require("integration_test.zigbee_test_utils").mock_hub_eui, 2) }) + + -- Read divisors/multipliers + test.socket.zigbee:__expect_send({ mock_inovelli_vzm32_sn.id, clusters.SimpleMetering.attributes.Multiplier:read(mock_inovelli_vzm32_sn) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm32_sn.id, clusters.ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_inovelli_vzm32_sn) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm32_sn.id, clusters.ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_inovelli_vzm32_sn) }) + + -- VZM32-specific: occupancy and illuminance reporting configuration + test.socket.zigbee:__expect_send({ mock_inovelli_vzm32_sn.id, device_management.build_bind_request(mock_inovelli_vzm32_sn, clusters.OccupancySensing.ID, require("integration_test.zigbee_test_utils").mock_hub_eui) }) + test.socket.zigbee:__expect_send({ mock_inovelli_vzm32_sn.id, clusters.IlluminanceMeasurement.attributes.MeasuredValue:configure_reporting(mock_inovelli_vzm32_sn, 10, 600, 11761) }) + + mock_inovelli_vzm32_sn:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn_child.lua b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn_child.lua new file mode 100644 index 0000000000..26346c04a9 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn_child.lua @@ -0,0 +1,345 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local cluster_base = require "st.zigbee.cluster_base" +local utils = require "st.utils" + +-- Device endpoints with supported clusters +local inovelli_vzm32_sn_endpoints = { + [1] = { + id = 1, + manufacturer = "Inovelli", + model = "VZM32-SN", + server_clusters = {0x0006, 0x0008, 0x0300} -- OnOff, Level, ColorControl + } +} + +local mock_parent_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("inovelli-vzm32-sn.yml"), + zigbee_endpoints = inovelli_vzm32_sn_endpoints, + fingerprinted_endpoint_id = 0x01 +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local mock_child_device = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("rgbw-bulb.yml"), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = "notification" +}) + +local function test_init() + test.mock_device.add_test_device(mock_parent_device) + test.mock_device.add_test_device(mock_child_device) +end +test.set_test_init_function(test_init) + +-- Test child device initialization +test.register_message_test( + "Child device should initialize with default color values", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_child_device.id, "added" }, + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorControl.hue(1)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorControl.saturation(1)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = 2700, maximum = 6500} })) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(6500)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.switchLevel.level(100)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.switch.switch("off")) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test child device switch on command +test.register_coroutine_test( + "Child device switch on should emit events and send configuration to parent", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local level = 100 -- Default level for child devices + local color = 100 -- Default color for child devices (since device starts with no hue state) + local effect = 1 -- Default notificationType + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switch", command = "on", args = {} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(notificationValue, 4, false, false) + ) + }) + end +) + +-- Test child device switch off command +test.register_coroutine_test( + "Child device switch off should emit events and send configuration to parent", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switch", command = "off", args = {} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("off")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(0, 4, false, false) + ) + }) + end +) + +-- Test child device level command +test.register_coroutine_test( + "Child device level command should emit events and send configuration to parent", + function() + local level = math.random(1, 99) + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local effect = 1 -- Default notificationType + local color = 100 -- Default color for child devices (since device starts with no hue state) + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) -- Use the actual level from command + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switchLevel", command = "setLevel", args = { level } } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switchLevel.level(level)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(notificationValue, 4, false, false) + ) + }) + end +) + +-- Test child device color command +test.register_coroutine_test( + "Child device color command should emit events and send configuration to parent", + function() + local color = math.random(0, 100) + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local level = 100 -- Default level for child devices + local effect = 1 -- Default notificationType + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "colorControl", command = "setColor", args = {{ hue = color, saturation = 100 }} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.hue(color)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.saturation(100)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(notificationValue, 4, false, false) + ) + }) + end +) + +-- Test child device color temperature command +test.register_coroutine_test( + "Child device color temperature command should emit events and send configuration to parent", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local level = 100 -- Default level for child devices + local color = 100 -- Default color for child devices (since device starts with no hue state) + local effect = 1 -- Default notificationType + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "colorTemperature", command = "setColorTemperature", args = { 3000 } } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.hue(100)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(3000)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zigbee:__expect_send({ + mock_parent_device.id, + cluster_base.build_manufacturer_specific_command( + mock_parent_device, + 0xFC31, -- PRIVATE_CLUSTER_ID + 0x01, -- PRIVATE_CMD_NOTIF_ID + 0x122F, -- MFG_CODE + utils.serialize_int(notificationValue, 4, false, false) + ) + }) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn_preferences.lua b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn_preferences.lua new file mode 100644 index 0000000000..28ae4829a0 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli_vzm32_sn_preferences.lua @@ -0,0 +1,163 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local data_types = require "st.zigbee.data_types" +local cluster_base = require "st.zigbee.cluster_base" +local utils = require "st.utils" + +-- Device endpoints with supported clusters +local inovelli_vzm32_sn_endpoints = { + [1] = { + id = 1, + manufacturer = "Inovelli", + model = "VZM32-SN", + server_clusters = {0x0006, 0x0008} -- OnOff, Level + } +} + +local mock_inovelli_vzm32_sn = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("inovelli-vzm32-sn.yml"), + zigbee_endpoints = inovelli_vzm32_sn_endpoints, + fingerprinted_endpoint_id = 0x01, + label = "Inovelli VZM32-SN" +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_inovelli_vzm32_sn) +end +test.set_test_init_function(test_init) + +-- Test parameter1 preference change +test.register_coroutine_test( + "parameter1 preference should send configuration command", + function() + local new_param_value = 50 + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm32_sn:generate_info_changed({preferences = {parameter1 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm32_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm32_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 1, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Uint8, + new_param_value + ) + }) + end +) + +-- Test parameter9 preference change +test.register_coroutine_test( + "parameter9 preference should send configuration command", + function() + local new_param_value = 10 + local expected_value = utils.round(new_param_value / 100 * 254) + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm32_sn:generate_info_changed({preferences = {parameter9 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm32_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm32_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 9, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Uint8, + expected_value + ) + }) + end +) + +-- Test parameter52 preference change +test.register_coroutine_test( + "parameter52 preference should send configuration command", + function() + local new_param_value = true + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm32_sn:generate_info_changed({preferences = {parameter52 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm32_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm32_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 52, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Boolean, + new_param_value + ) + }) + end +) + +-- Test parameter258 preference change +test.register_coroutine_test( + "parameter258 preference should send configuration command", + function() + local new_param_value = false + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm32_sn:generate_info_changed({preferences = {parameter258 = new_param_value}})) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm32_sn.id, + cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm32_sn, + 0xFC31, -- PRIVATE_CLUSTER_ID + 258, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Boolean, + new_param_value + ) + }) + end +) + +-- Test notificationChild preference change +test.register_coroutine_test( + "notificationChild preference should create child device when enabled", + function() + mock_inovelli_vzm32_sn:expect_device_create({ + type = "EDGE_CHILD", + label = "Inovelli VZM32-SN Notification", + profile = "rgbw-bulb-2700K-6500K", + parent_device_id = mock_inovelli_vzm32_sn.id, + parent_assigned_child_key = "notification" + }) + + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm32_sn:generate_info_changed({preferences = {notificationChild = true}})) + end +) + +-- Test parameter101 preference change +test.register_coroutine_test( + "parameter101 preference should send configuration command", + function() + local new_param_value = 200 + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzm32_sn:generate_info_changed({preferences = {parameter101 = new_param_value}})) + + local expected_command = cluster_base.write_manufacturer_specific_attribute( + mock_inovelli_vzm32_sn, + 0xFC32, -- PRIVATE_CLUSTER_ID + 101, -- parameter_number + 0x122F, -- MFG_CODE + data_types.Int16, + new_param_value + ) + + print("=== DEBUG: Expected command ===") + print("Command type:", type(expected_command)) + print("Command:", expected_command) + + test.socket.zigbee:__expect_send({ + mock_inovelli_vzm32_sn.id, + expected_command + }) + end +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_jasco_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_jasco_switch.lua index 750ef895f2..52ba3ef2a6 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_jasco_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_jasco_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_laisiao_bath_heather.lua b/drivers/SmartThings/zigbee-switch/src/test/test_laisiao_bath_heather.lua index ec90d234d8..0457c0eb62 100755 --- a/drivers/SmartThings/zigbee-switch/src/test/test_laisiao_bath_heather.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_laisiao_bath_heather.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch.lua index 4b5a10ecfb..1ff11bd754 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch_no_master.lua b/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch_no_master.lua index 6d1cf40e63..0f00d47fdb 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch_no_master.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch_no_master.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" @@ -480,6 +469,9 @@ test.register_coroutine_test( 3 ):to_endpoint(3) }) + test.socket.zigbee:__expect_send({ mock_base_device.id, OnOff.attributes.OnOff:read(mock_base_device) }) + test.socket.zigbee:__expect_send({ mock_base_device.id, OnOff.attributes.OnOff:read(mock_base_device):to_endpoint(2) }) + test.socket.zigbee:__expect_send({ mock_base_device.id, OnOff.attributes.OnOff:read(mock_base_device):to_endpoint(3) }) mock_base_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end ) diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch_power.lua b/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch_power.lua index c9e16aadad..4e35d5067d 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch_power.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch_power.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_on_off_zigbee_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_on_off_zigbee_bulb.lua index 51ea4fe9c6..7f0d412016 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_on_off_zigbee_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_on_off_zigbee_bulb.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_osram_iqbr30_light.lua b/drivers/SmartThings/zigbee-switch/src/test/test_osram_iqbr30_light.lua index aa366750d8..082aa0c5c2 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_osram_iqbr30_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_osram_iqbr30_light.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_osram_light.lua b/drivers/SmartThings/zigbee-switch/src/test/test_osram_light.lua index 3efe35fc49..8e2e847e2c 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_osram_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_osram_light.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_rgb_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_rgb_bulb.lua index 5f7e37f5eb..68af9fdedb 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_rgb_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_rgb_bulb.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_rgbw_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_rgbw_bulb.lua index 61a9fcf1ac..24ec471c9d 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_rgbw_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_rgbw_bulb.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_robb_smarrt_2-wire_dimmer.lua b/drivers/SmartThings/zigbee-switch/src/test/test_robb_smarrt_2-wire_dimmer.lua index d533d69489..1c815e2d7c 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_robb_smarrt_2-wire_dimmer.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_robb_smarrt_2-wire_dimmer.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_robb_smarrt_knob_dimmer.lua b/drivers/SmartThings/zigbee-switch/src/test/test_robb_smarrt_knob_dimmer.lua index d7e4db0f09..3ed587916c 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_robb_smarrt_knob_dimmer.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_robb_smarrt_knob_dimmer.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_sengled_color_temp_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_sengled_color_temp_bulb.lua index c528e22c92..e2ddba42d3 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_sengled_color_temp_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_sengled_color_temp_bulb.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_sengled_dimmer_bulb_with_motion_sensor.lua b/drivers/SmartThings/zigbee-switch/src/test/test_sengled_dimmer_bulb_with_motion_sensor.lua index 2ccaf7be36..e28c4de0b6 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_sengled_dimmer_bulb_with_motion_sensor.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_sengled_dimmer_bulb_with_motion_sensor.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_sinope_dimmer.lua b/drivers/SmartThings/zigbee-switch/src/test/test_sinope_dimmer.lua index d1aadf29a3..0859138a8c 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_sinope_dimmer.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_sinope_dimmer.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local base64 = require "st.base64" local cluster_base = require "st.zigbee.cluster_base" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_sinope_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_sinope_switch.lua index 35242deb6c..34e5f78640 100755 --- a/drivers/SmartThings/zigbee-switch/src/test/test_sinope_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_sinope_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local base64 = require "st.base64" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_switch_power.lua b/drivers/SmartThings/zigbee-switch/src/test/test_switch_power.lua index d4d427bc58..04c17b15de 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_switch_power.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_switch_power.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi.lua b/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi.lua index 45f3d4e2b3..a00f74bddf 100755 --- a/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi_switch.lua index 41924e891d..1ff11bd754 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_wallhero_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_wallhero_switch.lua index 26e72466c9..ce1a9e2882 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_wallhero_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_wallhero_switch.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_white_color_temp_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_white_color_temp_bulb.lua index eea20eb184..f024aa45be 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_white_color_temp_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_white_color_temp_bulb.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_yanmi_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_yanmi_switch.lua new file mode 100755 index 0000000000..61ad14e75d --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_yanmi_switch.lua @@ -0,0 +1,409 @@ +-- Copyright 2025 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. + +-- Mock out globals +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local OnOff = clusters.OnOff + +local common_switch_profile_def = t_utils.get_profile_definition("basic-switch.yml") + + +local mock_parent_device = test.mock_device.build_test_zigbee_device( + { + profile = common_switch_profile_def, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "JNL", + model = "Y-K003-001", + server_clusters = { 0003,0004,0005,0006 } + } + }, + fingerprinted_endpoint_id = 0x01 + } +) + +-- Switch 2 (Common Switch) +local mock_first_child = test.mock_device.build_test_child_device( + { + profile = common_switch_profile_def, + device_network_id = string.format("%04X:%02X", mock_parent_device:get_short_address(), 2), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = string.format("%02X", 2) + } +) + +-- Switch 3 (Common Switch) +local mock_second_child = test.mock_device.build_test_child_device( + { + profile = common_switch_profile_def, + device_network_id = string.format("%04X:%02X", mock_parent_device:get_short_address(), 3), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = string.format("%02X", 3) + } +) + + + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_parent_device) + test.mock_device.add_test_device(mock_first_child) + test.mock_device.add_test_device(mock_second_child) +end + +test.set_test_init_function(test_init) + +-- 4 Common Switch Tests +test.register_message_test( + "Reported on off status should be handled by parent device: on", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_parent_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, + true) :from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_parent_device:generate_test_message("main", capabilities.switch.switch.on()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_parent_device.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + } +) + +test.register_message_test( + "Reported on off status should be handled by first child device: on", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_first_child.id, OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, + true) :from_endpoint(0x02) } + }, + { + channel = "capability", + direction = "send", + message = mock_first_child:generate_test_message("main", capabilities.switch.switch.on()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_first_child.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + } +) + +test.register_message_test( + "Reported on off status should be handled by Second child device: on", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_second_child.id, OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, + true) :from_endpoint(0x03) } + }, + { + channel = "capability", + direction = "send", + message = mock_second_child:generate_test_message("main", capabilities.switch.switch.on()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_second_child.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + } +) + + +test.register_message_test( + "reported on off status should be handled by parent device: off", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_parent_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, + false) :from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_parent_device:generate_test_message("main", capabilities.switch.switch.off()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_parent_device.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + } +) + +test.register_message_test( + "Reported on off status should be handled by first child device: off", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_first_child.id, OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, + false) :from_endpoint(0x02) } + }, + { + channel = "capability", + direction = "send", + message = mock_first_child:generate_test_message("main", capabilities.switch.switch.off()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_first_child.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + } +) + +test.register_message_test( + "Reported on off status should be handled by Second child device: off", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_second_child.id, OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, + false) :from_endpoint(0x03) } + }, + { + channel = "capability", + direction = "send", + message = mock_second_child:generate_test_message("main", capabilities.switch.switch.off()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_second_child.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + } +) + + +test.register_message_test( + "Capability on command switch on should be handled : parent device", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "capability", + direction = "receive", + message = { mock_parent_device.id, { capability = "switch", component = "main", command = "on", args = { } } } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_parent_device.id, capability_id = "switch", capability_cmd_id = "on" } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x01) } + } + } +) + +test.register_message_test( + "Capability on command switch on should be handled : first child device", + { + { + channel = "capability", + direction = "receive", + message = { mock_first_child.id, { capability = "switch", component = "main", command = "on", args = { } } } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_first_child.id, capability_id = "switch", capability_cmd_id = "on" } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x02) } + } + } +) + +test.register_message_test( + "Capability on command switch on should be handled : second child device", + { + { + channel = "capability", + direction = "receive", + message = { mock_second_child.id, { capability = "switch", component = "main", command = "on", args = { } } } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_second_child.id, capability_id = "switch", capability_cmd_id = "on" } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x03) } + } + } +) + + + + +test.register_message_test( + "Capability off command switch off should be handled : parent device", + { + { + channel = "capability", + direction = "receive", + message = { mock_parent_device.id, { capability = "switch", component = "main", command = "off", args = { } } } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_parent_device.id, capability_id = "switch", capability_cmd_id = "off" } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x01) } + } + } +) + +test.register_message_test( + "Capability off command switch off should be handled : first child device", + { + { + channel = "capability", + direction = "receive", + message = { mock_first_child.id, { capability = "switch", component = "main", command = "off", args = { } } } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_first_child.id, capability_id = "switch", capability_cmd_id = "off" } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x02) } + } + } +) + +test.register_message_test( + "Capability off command switch off should be handled : second child device", + { + { + channel = "capability", + direction = "receive", + message = { mock_second_child.id, { capability = "switch", component = "main", command = "off", args = { } } } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_second_child.id, capability_id = "switch", capability_cmd_id = "off" } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x03) } + } + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_zigbee_ezex_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_zigbee_ezex_switch.lua index 88470b5489..8995523952 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_zigbee_ezex_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_zigbee_ezex_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_zigbee_metering_plug_power_consumption_report.lua b/drivers/SmartThings/zigbee-switch/src/test/test_zigbee_metering_plug_power_consumption_report.lua index 5128aa1cb4..d701bd89aa 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_zigbee_metering_plug_power_consumption_report.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_zigbee_metering_plug_power_consumption_report.lua @@ -1,22 +1,13 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" local SimpleMetering = clusters.SimpleMetering +local ElectricalMeasurement = clusters.ElectricalMeasurement +local OnOff = clusters.OnOff local zigbee_test_utils = require "integration_test.zigbee_test_utils" local t_utils = require "integration_test.utils" @@ -28,7 +19,7 @@ local mock_device = test.mock_device.build_test_zigbee_device( id = 1, manufacturer = "DAWON_DNS", model = "PM-B430-ZB", - server_clusters = {} + server_clusters = { 0x0000, 0x0002, 0x0003, 0x0006, 0x0702, 0x0B04 } } } } @@ -92,4 +83,69 @@ test.register_message_test( } ) +test.register_coroutine_test( + "Configure should configure all necessary attributes and refresh device", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, OnOff.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ElectricalMeasurement.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, SimpleMetering.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 300, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ElectricalMeasurement.attributes.ACPowerMultiplier:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ElectricalMeasurement.attributes.ACPowerDivisor:configure_reporting(mock_device, 1, 43200, 1) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ElectricalMeasurement.attributes.ActivePower:configure_reporting(mock_device, 5, 3600, 5) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + SimpleMetering.attributes.InstantaneousDemand:configure_reporting(mock_device, 5, 3600, 5) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:configure_reporting(mock_device, 5, 3600, 1) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ElectricalMeasurement.attributes.ActivePower:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, SimpleMetering.attributes.InstantaneousDemand:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_zigbee_metering_plug_rexense.lua b/drivers/SmartThings/zigbee-switch/src/test/test_zigbee_metering_plug_rexense.lua index a3be084eb2..e1519a4d1a 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_zigbee_metering_plug_rexense.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_zigbee_metering_plug_rexense.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_zll_color_temp_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_zll_color_temp_bulb.lua index fc68ec7786..1a13044f48 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_zll_color_temp_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_zll_color_temp_bulb.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_zll_dimmer.lua b/drivers/SmartThings/zigbee-switch/src/test/test_zll_dimmer.lua index eb590c3874..71305fba20 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_zll_dimmer.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_zll_dimmer.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_zll_dimmer_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_zll_dimmer_bulb.lua index a82dac084a..0ac17313d3 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_zll_dimmer_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_zll_dimmer_bulb.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_zll_rgb_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_zll_rgb_bulb.lua index 7bf6b175cd..3435b22288 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_zll_rgb_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_zll_rgb_bulb.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_zll_rgbw_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_zll_rgbw_bulb.lua index 7945b8d030..cf53f7e359 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_zll_rgbw_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_zll_rgbw_bulb.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-switch/src/tuya-multi/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/tuya-multi/can_handle.lua new file mode 100644 index 0000000000..c4878c95c2 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/tuya-multi/can_handle.lua @@ -0,0 +1,21 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_multi_endpoint(device) + local main_endpoint = device:get_endpoint(0x0006) + for _, ep in ipairs(device.zigbee_endpoints) do + if ep.id ~= main_endpoint then + return true + end + end + return false +end + +return function(opts, driver, device) + local TUYA_MFR_HEADER = "_TZ" + if string.sub(device:get_manufacturer(),1,3) == TUYA_MFR_HEADER and is_multi_endpoint(device) then -- if it is a tuya device, then send the magic packet + local subdriver = require("tuya-multi") + return true, subdriver + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/tuya-multi/init.lua b/drivers/SmartThings/zigbee-switch/src/tuya-multi/init.lua index c4857e9085..51c91ad768 100644 --- a/drivers/SmartThings/zigbee-switch/src/tuya-multi/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/tuya-multi/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" local data_types = require "st.zigbee.data_types" @@ -6,26 +9,6 @@ local messages = require "st.zigbee.messages" local zb_const = require "st.zigbee.constants" local read_attribute = require "st.zigbee.zcl.global_commands.read_attribute" -local TUYA_MFR_HEADER = "_TZ" - -local function is_multi_endpoint(device) - local main_endpoint = device:get_endpoint(clusters.OnOff.ID) - for _, ep in ipairs(device.zigbee_endpoints) do - if ep.id ~= main_endpoint then - return true - end - end - return false -end - -local function is_tuya_products(opts, driver, device) - if string.sub(device:get_manufacturer(),1,3) == TUYA_MFR_HEADER and is_multi_endpoint(device) then -- if it is a tuya device, then send the magic packet - local subdriver = require("tuya-multi") - return true, subdriver - end - return false -end - local function read_attribute_function(device, cluster_id, attr_id) local read_body = read_attribute.ReadAttribute( attr_id ) local zclh = zcl_messages.ZclHeader({ @@ -65,7 +48,7 @@ local tuya_switch_handler = { supported_capabilities = { capabilities.switch }, - can_handle = is_tuya_products + can_handle = require("tuya-multi.can_handle"), } -return tuya_switch_handler \ No newline at end of file +return tuya_switch_handler diff --git a/drivers/SmartThings/zigbee-switch/src/wallhero/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/wallhero/can_handle.lua new file mode 100644 index 0000000000..69bcf73fce --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/wallhero/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require "wallhero.fingerprints" + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("wallhero") + return true, subdriver + end + end + return false +end + diff --git a/drivers/SmartThings/zigbee-switch/src/wallhero/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/wallhero/fingerprints.lua new file mode 100644 index 0000000000..9d58e6457a --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/wallhero/fingerprints.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "WALL HERO", model = "ACL-401S4I", switches = 4, buttons = 0 }, + { mfr = "WALL HERO", model = "ACL-401S8I", switches = 4, buttons = 4 }, + { mfr = "WALL HERO", model = "ACL-401S3I", switches = 3, buttons = 0 }, + { mfr = "WALL HERO", model = "ACL-401S2I", switches = 2, buttons = 0 }, + { mfr = "WALL HERO", model = "ACL-401S1I", switches = 1, buttons = 0 }, + { mfr = "WALL HERO", model = "ACL-401ON", switches = 1, buttons = 0 } +} diff --git a/drivers/SmartThings/zigbee-switch/src/wallhero/init.lua b/drivers/SmartThings/zigbee-switch/src/wallhero/init.lua index 79f3110e41..1e0e7eb26e 100644 --- a/drivers/SmartThings/zigbee-switch/src/wallhero/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/wallhero/init.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local log = require "log" @@ -25,26 +14,8 @@ local PRIVATE_CLUSTER_ID = 0x0006 local PRIVATE_ATTRIBUTE_ID = 0x6000 local MFG_CODE = 0x1235 -local FINGERPRINTS = { - { mfr = "WALL HERO", model = "ACL-401S4I", switches = 4, buttons = 0 }, - { mfr = "WALL HERO", model = "ACL-401S8I", switches = 4, buttons = 4 }, - { mfr = "WALL HERO", model = "ACL-401S3I", switches = 3, buttons = 0 }, - { mfr = "WALL HERO", model = "ACL-401S2I", switches = 2, buttons = 0 }, - { mfr = "WALL HERO", model = "ACL-401S1I", switches = 1, buttons = 0 }, - { mfr = "WALL HERO", model = "ACL-401ON", switches = 1, buttons = 0 } -} - -local function can_handle_wallhero_switch(opts, driver, device, ...) - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("wallhero") - return true, subdriver - end - end - return false -end - local function get_children_info(device) + local FINGERPRINTS = require "wallhero.fingerprints" for _, fingerprint in ipairs(FINGERPRINTS) do if device:get_model() == fingerprint.model then return fingerprint.switches, fingerprint.buttons @@ -141,7 +112,7 @@ local wallheroswitch = { } } }, - can_handle = can_handle_wallhero_switch + can_handle = require("wallhero.can_handle"), } return wallheroswitch diff --git a/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/can_handle.lua new file mode 100644 index 0000000000..d45b7b745f --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local WHITE_COLOR_TEMP_BULB_FINGERPRINTS = require "white-color-temp-bulb.fingerprints" + local can_handle = (WHITE_COLOR_TEMP_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()] + if can_handle then + local subdriver = require("white-color-temp-bulb") + return true, subdriver + else + return false + end +end diff --git a/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/duragreen/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/duragreen/can_handle.lua new file mode 100644 index 0000000000..b3b8bd591b --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/duragreen/can_handle.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local DURAGREEN_BULB_FINGERPRINTS = { + ["DURAGREEN"] = { + ["DG-CW-02"] = true, + ["DG-CW-01"] = true, + ["DG-CCT-01"] = true + }, + } + local res = (DURAGREEN_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()] or false + if res then + return res, require("white-color-temp-bulb.duragreen") + else + return res + end +end diff --git a/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/duragreen/init.lua b/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/duragreen/init.lua index 7df79be32e..b6f96842c3 100644 --- a/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/duragreen/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/duragreen/init.lua @@ -1,34 +1,11 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" local Level = clusters.Level -local DURAGREEN_BULB_FINGERPRINTS = { - ["DURAGREEN"] = { - ["DG-CW-02"] = true, - ["DG-CW-01"] = true, - ["DG-CCT-01"] = true - }, -} - -local function can_handle_duragreen_bulb(opts, driver, device) - return (DURAGREEN_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()] or false -end - local function handle_set_level(driver, device, cmd) local level = math.floor(cmd.args.level/100.0 * 254) local transtition_time = cmd.args.rate or 0xFFFF @@ -46,7 +23,7 @@ local duragreen_color_temp_bulb = { [capabilities.switchLevel.commands.setLevel.NAME] = handle_set_level } }, - can_handle = can_handle_duragreen_bulb + can_handle = require("white-color-temp-bulb.duragreen.can_handle") } return duragreen_color_temp_bulb diff --git a/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/fingerprints.lua new file mode 100644 index 0000000000..02bb28e88c --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/fingerprints.lua @@ -0,0 +1,99 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + ["DURAGREEN"] = { + ["DG-CW-02"] = true, + ["DG-CW-01"] = true, + ["DG-CCT-01"] = true + }, + ["Samsung Electronics"] = { + ["ABL-LIGHT-Z-001"] = true, + ["SAMSUNG-ITM-Z-001"] = true + }, + ["Juno"] = { + ["ABL-LIGHT-Z-001"] = true + }, + ["AduroSmart Eria"] = { + ["AD-ColorTemperature3001"] = true + }, + ["Aurora"] = { + ["TWBulb51AU"] = true, + ["TWMPROZXBulb50AU"] = true, + ["TWStrip50AU"] = true, + ["TWGU10Bulb50AU"] = true, + ["TWCLBulb50AU"] = true + }, + ["CWD"] = { + ["ZB.A806Ecct-A001"] = true, + ["ZB.A806Bcct-A001"] = true, + ["ZB.M350cct-A001"] = true + }, + ["ETI"] = { + ["Zigbee CCT Downlight"] = true + }, + ["The Home Depot"] = { + ["Ecosmart-ZBT-BR30-CCT-Bulb"] = true, + ["Ecosmart-ZBT-A19-CCT-Bulb"] = true + }, + ["IKEA of Sweden"] = { + ["GUNNARP panel round"] = true, + ["LEPTITER Recessed spot light"] = true, + ["TRADFRI bulb E12 WS opal 600lm"] = true, + ["TRADFRI bulb E14 WS 470lm"] = true, + ["TRADFRI bulb E14 WS opal 600lm"] = true, + ["TRADFRI bulb E26 WS clear 806lm"] = true, + ["TRADFRI bulb E27 WS clear 806lm"] = true, + ["TRADFRI bulb E26 WS opal 1000lm"] = true, + ["TRADFRI bulb E27 WS opal 1000lm"] = true + }, + ["Megaman"] = { + ["Z3-ColorTemperature"] = true + }, + ["innr"] = { + ["RB 248 T"] = true, + ["RB 278 T"] = true, + ["RS 228 T"] = true + }, + ["OSRAM"] = { + ["LIGHTIFY BR Tunable White"] = true, + ["LIGHTIFY RT Tunable White"] = true, + ["Classic A60 TW"] = true, + ["LIGHTIFY A19 Tunable White"] = true, + ["Classic B40 TW - LIGHTIFY"] = true, + ["LIGHTIFY Conv Under Cabinet TW"] = true, + ["ColorstripRGBW"] = true, + ["LIGHTIFY Edge-lit Flushmount TW"] = true, + ["LIGHTIFY Surface TW"] = true, + ["LIGHTIFY Under Cabinet TW"] = true, + ["LIGHTIFY Edge-lit flushmount"] = true + }, + ["LEDVANCE"] = { + ["A19 TW 10 year"] = true, + ["MR16 TW"] = true, + ["BR30 TW"] = true, + ["RT TW"] = true + }, + ["Smarthome"] = { + ["S111-202A"] = true + }, + ["lk"] = { + ["ZBT-CCTLight-GLS0108"] = true + }, + ["MLI"] = { + ["ZBT-ColorTemperature"] = true + }, + ["sengled"] = { + ["Z01-A19NAE26"] = true, + ["Z01-A191AE26W"] = true, + ["Z01-A60EAB22"] = true, + ["Z01-A60EAE27"] = true + }, + ["Third Reality, Inc"] = { + ["3RSL011Z"] = true, + ["3RSL012Z"] = true + }, + ["Ajax Online"] = { + ["CCT"] = true + } +} diff --git a/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/init.lua b/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/init.lua index 70efb29afa..25bc13b65f 100644 --- a/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -18,113 +7,6 @@ local colorTemperature_defaults = require "st.zigbee.defaults.colorTemperature_d local ColorControl = clusters.ColorControl -local WHITE_COLOR_TEMP_BULB_FINGERPRINTS = { - ["DURAGREEN"] = { - ["DG-CW-02"] = true, - ["DG-CW-01"] = true, - ["DG-CCT-01"] = true - }, - ["Samsung Electronics"] = { - ["ABL-LIGHT-Z-001"] = true, - ["SAMSUNG-ITM-Z-001"] = true - }, - ["Juno"] = { - ["ABL-LIGHT-Z-001"] = true - }, - ["AduroSmart Eria"] = { - ["AD-ColorTemperature3001"] = true - }, - ["Aurora"] = { - ["TWBulb51AU"] = true, - ["TWMPROZXBulb50AU"] = true, - ["TWStrip50AU"] = true, - ["TWGU10Bulb50AU"] = true, - ["TWCLBulb50AU"] = true - }, - ["CWD"] = { - ["ZB.A806Ecct-A001"] = true, - ["ZB.A806Bcct-A001"] = true, - ["ZB.M350cct-A001"] = true - }, - ["ETI"] = { - ["Zigbee CCT Downlight"] = true - }, - ["The Home Depot"] = { - ["Ecosmart-ZBT-BR30-CCT-Bulb"] = true, - ["Ecosmart-ZBT-A19-CCT-Bulb"] = true - }, - ["IKEA of Sweden"] = { - ["GUNNARP panel round"] = true, - ["LEPTITER Recessed spot light"] = true, - ["TRADFRI bulb E12 WS opal 600lm"] = true, - ["TRADFRI bulb E14 WS 470lm"] = true, - ["TRADFRI bulb E14 WS opal 600lm"] = true, - ["TRADFRI bulb E26 WS clear 806lm"] = true, - ["TRADFRI bulb E27 WS clear 806lm"] = true, - ["TRADFRI bulb E26 WS opal 1000lm"] = true, - ["TRADFRI bulb E27 WS opal 1000lm"] = true - }, - ["Megaman"] = { - ["Z3-ColorTemperature"] = true - }, - ["innr"] = { - ["RB 248 T"] = true, - ["RB 278 T"] = true, - ["RS 228 T"] = true - }, - ["OSRAM"] = { - ["LIGHTIFY BR Tunable White"] = true, - ["LIGHTIFY RT Tunable White"] = true, - ["Classic A60 TW"] = true, - ["LIGHTIFY A19 Tunable White"] = true, - ["Classic B40 TW - LIGHTIFY"] = true, - ["LIGHTIFY Conv Under Cabinet TW"] = true, - ["ColorstripRGBW"] = true, - ["LIGHTIFY Edge-lit Flushmount TW"] = true, - ["LIGHTIFY Surface TW"] = true, - ["LIGHTIFY Under Cabinet TW"] = true, - ["LIGHTIFY Edge-lit flushmount"] = true - }, - ["LEDVANCE"] = { - ["A19 TW 10 year"] = true, - ["MR16 TW"] = true, - ["BR30 TW"] = true, - ["RT TW"] = true - }, - ["Smarthome"] = { - ["S111-202A"] = true - }, - ["lk"] = { - ["ZBT-CCTLight-GLS0108"] = true - }, - ["MLI"] = { - ["ZBT-ColorTemperature"] = true - }, - ["sengled"] = { - ["Z01-A19NAE26"] = true, - ["Z01-A191AE26W"] = true, - ["Z01-A60EAB22"] = true, - ["Z01-A60EAE27"] = true - }, - ["Third Reality, Inc"] = { - ["3RSL011Z"] = true, - ["3RSL012Z"] = true - }, - ["Ajax Online"] = { - ["CCT"] = true - } -} - -local function can_handle_white_color_temp_bulb(opts, driver, device) - local can_handle = (WHITE_COLOR_TEMP_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()] - if can_handle then - local subdriver = require("white-color-temp-bulb") - return true, subdriver - else - return false - end -end - local function set_color_temperature_handler(driver, device, cmd) colorTemperature_defaults.set_color_temperature(driver, device, cmd) @@ -140,10 +22,8 @@ local white_color_temp_bulb = { [capabilities.colorTemperature.commands.setColorTemperature.NAME] = set_color_temperature_handler } }, - sub_drivers = { - require("white-color-temp-bulb.duragreen"), - }, - can_handle = can_handle_white_color_temp_bulb + sub_drivers = require("white-color-temp-bulb.sub_drivers"), + can_handle = require("white-color-temp-bulb.can_handle"), } return white_color_temp_bulb diff --git a/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/sub_drivers.lua new file mode 100644 index 0000000000..9beaf743bc --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/sub_drivers.lua @@ -0,0 +1,7 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load = require "lazy_load_subdriver" +return { + lazy_load("white-color-temp-bulb.duragreen"), +} diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-dimmer-power-energy/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-dimmer-power-energy/can_handle.lua new file mode 100644 index 0000000000..7b13c4b630 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-dimmer-power-energy/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_DIMMER_POWER_ENERGY_FINGERPRINTS = { + { mfr = "Jasco Products", model = "43082" } +} + +return function(opts, driver, device) + for _, fingerprint in ipairs(ZIGBEE_DIMMER_POWER_ENERGY_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("zigbee-dimmer-power-energy") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-dimmer-power-energy/init.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-dimmer-power-energy/init.lua index 75f2af82c1..dc37bf1181 100644 --- a/drivers/SmartThings/zigbee-switch/src/zigbee-dimmer-power-energy/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-dimmer-power-energy/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" @@ -18,20 +7,6 @@ local SimpleMetering = clusters.SimpleMetering local constants = require "st.zigbee.constants" local configurations = require "configurations" -local ZIGBEE_DIMMER_POWER_ENERGY_FINGERPRINTS = { - { mfr = "Jasco Products", model = "43082" } -} - -local is_zigbee_dimmer_power_energy = function(opts, driver, device) - for _, fingerprint in ipairs(ZIGBEE_DIMMER_POWER_ENERGY_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("zigbee-dimmer-power-energy") - return true, subdriver - end - end - return false -end - local device_init = function(self, device) local customEnergyDivisor = 10000 device:set_field(constants.SIMPLE_METERING_DIVISOR_KEY, customEnergyDivisor, {persist = true}) @@ -69,7 +44,7 @@ local zigbee_dimmer_power_energy_handler = { init = configurations.power_reconfig_wrapper(device_init), doConfigure = do_configure, }, - can_handle = is_zigbee_dimmer_power_energy + can_handle = require("zigbee-dimmer-power-energy.can_handle"), } return zigbee_dimmer_power_energy_handler diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/can_handle.lua new file mode 100644 index 0000000000..cbbc8dfeab --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local DIMMING_LIGHT_FINGERPRINTS = require "zigbee-dimming-light.fingerprints" + for _, fingerprint in ipairs(DIMMING_LIGHT_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("zigbee-dimming-light") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/fingerprints.lua new file mode 100644 index 0000000000..820bd633b6 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/fingerprints.lua @@ -0,0 +1,36 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + {mfr = "Vimar", model = "DimmerSwitch_v1.0"}, -- Vimar Smart Dimmer Switch + {mfr = "OSRAM", model = "LIGHTIFY A19 ON/OFF/DIM"}, -- SYLVANIA Smart A19 Soft White + {mfr = "OSRAM", model = "LIGHTIFY A19 ON/OFF/DIM 10 Year"}, -- SYLVANIA Smart 10-Year A19 + {mfr = "OSRAM SYLVANIA", model = "iQBR30"}, -- SYLVANIA Ultra iQ + {mfr = "OSRAM", model = "LIGHTIFY PAR38 ON/OFF/DIM"}, -- SYLVANIA Smart PAR38 Soft White + {mfr = "OSRAM", model = "LIGHTIFY BR ON/OFF/DIM"}, -- SYLVANIA Smart BR30 Soft White + {mfr = "sengled", model = "E11-G13"}, -- Sengled Element Classic + {mfr = "sengled", model = "E11-G14"}, -- Sengled Element Classic + {mfr = "sengled", model = "E11-G23"}, -- Sengled Element Classic + {mfr = "sengled", model = "E11-G33"}, -- Sengled Element Classic + {mfr = "sengled", model = "E12-N13"}, -- Sengled Element Classic + {mfr = "sengled", model = "E12-N14"}, -- Sengled Element Classic + {mfr = "sengled", model = "E12-N15"}, -- Sengled Element Classic + {mfr = "sengled", model = "E11-N13"}, -- Sengled Element Classic + {mfr = "sengled", model = "E11-N14"}, -- Sengled Element Classic + {mfr = "sengled", model = "E1A-AC2"}, -- Sengled DownLight + {mfr = "sengled", model = "E11-N13A"}, -- Sengled Extra Bright Soft White + {mfr = "sengled", model = "E11-N14A"}, -- Sengled Extra Bright Daylight + {mfr = "sengled", model = "E21-N13A"}, -- Sengled Soft White + {mfr = "sengled", model = "E21-N14A"}, -- Sengled Daylight + {mfr = "sengled", model = "E11-U21U31"}, -- Sengled Element Touch + {mfr = "sengled", model = "E13-A21"}, -- Sengled LED Flood Light + {mfr = "sengled", model = "E11-N1G"}, -- Sengled Smart LED Vintage Edison Bulb + {mfr = "sengled", model = "E23-N11"}, -- Sengled Element Classic par38 + {mfr = "Leviton", model = "DL6HD"}, -- Leviton Dimmer Switch + {mfr = "Leviton", model = "DL3HL"}, -- Leviton Lumina RF Plug-In Dimmer + {mfr = "Leviton", model = "DL1KD"}, -- Leviton Lumina RF Dimmer Switch + {mfr = "Leviton", model = "ZSD07"}, -- Leviton Lumina RF 0-10V Dimming Wall Switch + {mfr = "MRVL", model = "MZ100"}, + {mfr = "CREE", model = "Connected A-19 60W Equivalent"}, + {mfr = "Insta GmbH", model = "NEXENTRO Dimming Actuator"} +} diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/init.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/init.lua index 68e924a8f1..dc8629c57b 100644 --- a/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" @@ -20,40 +9,6 @@ local switch_utils = require "switch_utils" local OnOff = clusters.OnOff local Level = clusters.Level -local DIMMING_LIGHT_FINGERPRINTS = { - {mfr = "Vimar", model = "DimmerSwitch_v1.0"}, -- Vimar Smart Dimmer Switch - {mfr = "OSRAM", model = "LIGHTIFY A19 ON/OFF/DIM"}, -- SYLVANIA Smart A19 Soft White - {mfr = "OSRAM", model = "LIGHTIFY A19 ON/OFF/DIM 10 Year"}, -- SYLVANIA Smart 10-Year A19 - {mfr = "OSRAM SYLVANIA", model = "iQBR30"}, -- SYLVANIA Ultra iQ - {mfr = "OSRAM", model = "LIGHTIFY PAR38 ON/OFF/DIM"}, -- SYLVANIA Smart PAR38 Soft White - {mfr = "OSRAM", model = "LIGHTIFY BR ON/OFF/DIM"}, -- SYLVANIA Smart BR30 Soft White - {mfr = "sengled", model = "E11-G13"}, -- Sengled Element Classic - {mfr = "sengled", model = "E11-G14"}, -- Sengled Element Classic - {mfr = "sengled", model = "E11-G23"}, -- Sengled Element Classic - {mfr = "sengled", model = "E11-G33"}, -- Sengled Element Classic - {mfr = "sengled", model = "E12-N13"}, -- Sengled Element Classic - {mfr = "sengled", model = "E12-N14"}, -- Sengled Element Classic - {mfr = "sengled", model = "E12-N15"}, -- Sengled Element Classic - {mfr = "sengled", model = "E11-N13"}, -- Sengled Element Classic - {mfr = "sengled", model = "E11-N14"}, -- Sengled Element Classic - {mfr = "sengled", model = "E1A-AC2"}, -- Sengled DownLight - {mfr = "sengled", model = "E11-N13A"}, -- Sengled Extra Bright Soft White - {mfr = "sengled", model = "E11-N14A"}, -- Sengled Extra Bright Daylight - {mfr = "sengled", model = "E21-N13A"}, -- Sengled Soft White - {mfr = "sengled", model = "E21-N14A"}, -- Sengled Daylight - {mfr = "sengled", model = "E11-U21U31"}, -- Sengled Element Touch - {mfr = "sengled", model = "E13-A21"}, -- Sengled LED Flood Light - {mfr = "sengled", model = "E11-N1G"}, -- Sengled Smart LED Vintage Edison Bulb - {mfr = "sengled", model = "E23-N11"}, -- Sengled Element Classic par38 - {mfr = "Leviton", model = "DL6HD"}, -- Leviton Dimmer Switch - {mfr = "Leviton", model = "DL3HL"}, -- Leviton Lumina RF Plug-In Dimmer - {mfr = "Leviton", model = "DL1KD"}, -- Leviton Lumina RF Dimmer Switch - {mfr = "Leviton", model = "ZSD07"}, -- Leviton Lumina RF 0-10V Dimming Wall Switch - {mfr = "MRVL", model = "MZ100"}, - {mfr = "CREE", model = "Connected A-19 60W Equivalent"}, - {mfr = "Insta GmbH", model = "NEXENTRO Dimming Actuator"} -} - local DIMMING_LIGHT_CONFIGURATION = { { cluster = OnOff.ID, @@ -75,16 +30,6 @@ local DIMMING_LIGHT_CONFIGURATION = { } } -local function can_handle_zigbee_dimming_light(opts, driver, device) - for _, fingerprint in ipairs(DIMMING_LIGHT_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("zigbee-dimming-light") - return true, subdriver - end - end - return false -end - local function do_configure(driver, device) device:refresh() device:configure() @@ -107,11 +52,8 @@ local zigbee_dimming_light = { added = device_added, doConfigure = do_configure }, - sub_drivers = { - require("zigbee-dimming-light/osram-iqbr30"), - require("zigbee-dimming-light/zll-dimmer") - }, - can_handle = can_handle_zigbee_dimming_light + sub_drivers = require("zigbee-dimming-light.sub_drivers"), + can_handle = require("zigbee-dimming-light.can_handle"), } return zigbee_dimming_light diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/osram-iqbr30/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/osram-iqbr30/can_handle.lua new file mode 100644 index 0000000000..8463ce2a77 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/osram-iqbr30/can_handle.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local res = device:get_manufacturer() == "OSRAM SYLVANIA" and device:get_model() == "iQBR30" + if res then + return res, require("zigbee-dimming-light.osram-iqbr30") + end + return res +end diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/osram-iqbr30/init.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/osram-iqbr30/init.lua index c6d31b7e10..74d9acb903 100644 --- a/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/osram-iqbr30/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/osram-iqbr30/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -20,10 +9,6 @@ local Level = clusters.Level local SwitchLevel = capabilities.switchLevel -local function can_handle_osram_iqbr30(opts, driver, device, ...) - return device:get_manufacturer() == "OSRAM SYLVANIA" and device:get_model() == "iQBR30" -end - local function set_switch_level_handler(driver, device, cmd) local level = math.floor(cmd.args.level / 100.0 * 254) @@ -40,7 +25,7 @@ local osram_iqbr30 = { [SwitchLevel.commands.setLevel.NAME] = set_switch_level_handler } }, - can_handle = can_handle_osram_iqbr30 + can_handle = require("zigbee-dimming-light.osram-iqbr30.can_handle"), } return osram_iqbr30 diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/sub_drivers.lua new file mode 100644 index 0000000000..fa188a4e7e --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/sub_drivers.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load = require "lazy_load_subdriver" + +return { + lazy_load("zigbee-dimming-light.osram-iqbr30"), + lazy_load("zigbee-dimming-light.zll-dimmer") +} diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/zll-dimmer/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/zll-dimmer/can_handle.lua new file mode 100644 index 0000000000..d75b86b353 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/zll-dimmer/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local ZLL_DIMMER_FINGERPRINTS = require("zigbee-dimming-light.zll-dimmer.fingerprints") + for _, fingerprint in ipairs(ZLL_DIMMER_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-dimming-light.zll-dimmer") + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/zll-dimmer/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/zll-dimmer/fingerprints.lua new file mode 100644 index 0000000000..1a5176bcd5 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/zll-dimmer/fingerprints.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + {mfr = "Leviton", model = "DL6HD"}, -- Leviton Dimmer Switch + {mfr = "Leviton", model = "DL3HL"}, -- Leviton Lumina RF Plug-In Dimmer + {mfr = "Leviton", model = "DL1KD"}, -- Leviton Lumina RF Dimmer Switch + {mfr = "Leviton", model = "ZSD07"}, -- Leviton Lumina RF 0-10V Dimming Wall Switch + {mfr = "MRVL", model = "MZ100"}, + {mfr = "CREE", model = "Connected A-19 60W Equivalent"} +} diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/zll-dimmer/init.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/zll-dimmer/init.lua index 72b35fd580..039b049fc7 100644 --- a/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/zll-dimmer/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/zll-dimmer/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -19,24 +8,6 @@ local Level = clusters.Level local SwitchLevel = capabilities.switchLevel -local ZLL_DIMMER_FINGERPRINTS = { - {mfr = "Leviton", model = "DL6HD"}, -- Leviton Dimmer Switch - {mfr = "Leviton", model = "DL3HL"}, -- Leviton Lumina RF Plug-In Dimmer - {mfr = "Leviton", model = "DL1KD"}, -- Leviton Lumina RF Dimmer Switch - {mfr = "Leviton", model = "ZSD07"}, -- Leviton Lumina RF 0-10V Dimming Wall Switch - {mfr = "MRVL", model = "MZ100"}, - {mfr = "CREE", model = "Connected A-19 60W Equivalent"} -} - -local function can_handle_zll_dimmer(opts, driver, device) - for _, fingerprint in ipairs(ZLL_DIMMER_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end - local function set_switch_level_handler(driver, device, cmd) local level = math.floor(cmd.args.level / 100.0 * 254) @@ -51,7 +22,7 @@ local zll_dimmer = { [SwitchLevel.commands.setLevel.NAME] = set_switch_level_handler } }, - can_handle = can_handle_zll_dimmer + can_handle = require("zigbee-dimming-light.zll-dimmer.can_handle") } return zll_dimmer diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-dual-metering-switch/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-dual-metering-switch/can_handle.lua new file mode 100644 index 0000000000..fbc679fb18 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-dual-metering-switch/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZIGBEE_DUAL_METERING_SWITCH_FINGERPRINT = { + {mfr = "Aurora", model = "DoubleSocket50AU"} +} + +return function(opts, driver, device, ...) + for _, fingerprint in ipairs(ZIGBEE_DUAL_METERING_SWITCH_FINGERPRINT) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("zigbee-dual-metering-switch") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-dual-metering-switch/init.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-dual-metering-switch/init.lua index 68f7d63675..b1d10f94a6 100644 --- a/drivers/SmartThings/zigbee-switch/src/zigbee-dual-metering-switch/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-dual-metering-switch/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local st_device = require "st.device" local clusters = require "st.zigbee.zcl.clusters" @@ -21,20 +10,6 @@ local configurations = require "configurations" local CHILD_ENDPOINT = 2 -local ZIGBEE_DUAL_METERING_SWITCH_FINGERPRINT = { - {mfr = "Aurora", model = "DoubleSocket50AU"} -} - -local function can_handle_zigbee_dual_metering_switch(opts, driver, device, ...) - for _, fingerprint in ipairs(ZIGBEE_DUAL_METERING_SWITCH_FINGERPRINT) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("zigbee-dual-metering-switch") - return true, subdriver - end - end - return false -end - local function do_refresh(self, device) device:send(OnOff.attributes.OnOff:read(device)) device:send(ElectricalMeasurement.attributes.ActivePower:read(device)) @@ -80,7 +55,7 @@ local zigbee_dual_metering_switch = { init = configurations.power_reconfig_wrapper(device_init), added = device_added }, - can_handle = can_handle_zigbee_dual_metering_switch + can_handle = require("zigbee-dual-metering-switch.can_handle"), } return zigbee_dual_metering_switch diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-metering-plug-power-consumption-report/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-metering-plug-power-consumption-report/can_handle.lua new file mode 100644 index 0000000000..e6e3165aa3 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-metering-plug-power-consumption-report/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local can_handle = device:get_manufacturer() == "DAWON_DNS" + if can_handle then + local subdriver = require("zigbee-metering-plug-power-consumption-report") + return true, subdriver + else + return false + end +end diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-metering-plug-power-consumption-report/init.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-metering-plug-power-consumption-report/init.lua index 044bf509df..53a7e1eb99 100644 --- a/drivers/SmartThings/zigbee-switch/src/zigbee-metering-plug-power-consumption-report/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-metering-plug-power-consumption-report/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" @@ -35,6 +24,7 @@ end local do_configure = function(self, device) device:configure() + device:refresh() end local device_init = function(self, device) @@ -54,15 +44,7 @@ local zigbee_metering_plug_power_conumption_report = { init = configurations.power_reconfig_wrapper(device_init), doConfigure = do_configure }, - can_handle = function(opts, driver, device, ...) - local can_handle = device:get_manufacturer() == "DAWON_DNS" - if can_handle then - local subdriver = require("zigbee-metering-plug-power-consumption-report") - return true, subdriver - else - return false - end - end + can_handle = require("zigbee-metering-plug-power-consumption-report.can_handle"), } return zigbee_metering_plug_power_conumption_report diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/aurora-relay/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/aurora-relay/can_handle.lua new file mode 100644 index 0000000000..18cff448c1 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/aurora-relay/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +return function(opts, driver, device) + local AURORA_RELAY_FINGERPRINTS = { + { mfr = "Aurora", model = "Smart16ARelay51AU" }, + { mfr = "Develco Products A/S", model = "Smart16ARelay51AU" }, + { mfr = "SALUS", model = "SX885ZB" } + } + for _, fingerprint in ipairs(AURORA_RELAY_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-switch-power.aurora-relay") + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/aurora-relay/init.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/aurora-relay/init.lua index 0f8eac96bc..266ff9b4e7 100644 --- a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/aurora-relay/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/aurora-relay/init.lua @@ -1,34 +1,8 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local constants = require "st.zigbee.constants" -local AURORA_RELAY_FINGERPRINTS = { - { mfr = "Aurora", model = "Smart16ARelay51AU" }, - { mfr = "Develco Products A/S", model = "Smart16ARelay51AU" }, - { mfr = "SALUS", model = "SX885ZB" } -} - -local function can_handle_aurora_relay(opts, driver, device) - for _, fingerprint in ipairs(AURORA_RELAY_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end - local function do_configure(driver, device) device:configure() @@ -43,7 +17,7 @@ local aurora_relay = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_aurora_relay + can_handle = require("zigbee-switch-power.aurora-relay.can_handle"), } return aurora_relay diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/can_handle.lua new file mode 100644 index 0000000000..ed4db3dc52 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local SWITCH_POWER_FINGERPRINTS = require "zigbee-switch-power.fingerprints" + for _, fingerprint in ipairs(SWITCH_POWER_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("zigbee-switch-power") + return true, subdriver + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/fingerprints.lua new file mode 100644 index 0000000000..d277d36967 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/fingerprints.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "Vimar", model = "Mains_Power_Outlet_v1.0" }, + { model = "PAN18-v1.0.7" }, + { model = "E210-KR210Z1-HA" }, + { mfr = "Aurora", model = "Smart16ARelay51AU" }, + { mfr = "Develco Products A/S", model = "Smart16ARelay51AU" }, + { mfr = "Jasco Products", model = "45853" }, + { mfr = "Jasco Products", model = "45856" }, + { mfr = "MEGAMAN", model = "SH-PSUKC44B-E" }, + { mfr = "ClimaxTechnology", model = "PSM_00.00.00.35TC" }, + { mfr = "SALUS", model = "SX885ZB" }, + { mfr = "AduroSmart Eria", model = "AD-SmartPlug3001" }, + { mfr = "AduroSmart Eria", model = "BPU3" }, + { mfr = "AduroSmart Eria", model = "BDP3001" } +} diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/init.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/init.lua index d9ae8f4750..0ea2224a6f 100644 --- a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -19,32 +8,6 @@ local constants = require "st.zigbee.constants" local SimpleMetering = clusters.SimpleMetering local ElectricalMeasurement = clusters.ElectricalMeasurement -local SWITCH_POWER_FINGERPRINTS = { - { mfr = "Vimar", model = "Mains_Power_Outlet_v1.0" }, - { model = "PAN18-v1.0.7" }, - { model = "E210-KR210Z1-HA" }, - { mfr = "Aurora", model = "Smart16ARelay51AU" }, - { mfr = "Develco Products A/S", model = "Smart16ARelay51AU" }, - { mfr = "Jasco Products", model = "45853" }, - { mfr = "Jasco Products", model = "45856" }, - { mfr = "MEGAMAN", model = "SH-PSUKC44B-E" }, - { mfr = "ClimaxTechnology", model = "PSM_00.00.00.35TC" }, - { mfr = "SALUS", model = "SX885ZB" }, - { mfr = "AduroSmart Eria", model = "AD-SmartPlug3001" }, - { mfr = "AduroSmart Eria", model = "BPU3" }, - { mfr = "AduroSmart Eria", model = "BDP3001" } -} - -local function can_handle_zigbee_switch_power(opts, driver, device) - for _, fingerprint in ipairs(SWITCH_POWER_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("zigbee-switch-power") - return true, subdriver - end - end - return false -end - local function active_power_meter_handler(driver, device, value, zb_rx) local raw_value = value.value local divisor = device:get_field(constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY) or 10 @@ -75,11 +38,8 @@ local zigbee_switch_power = { } } }, - sub_drivers = { - require("zigbee-switch-power/aurora-relay"), - require("zigbee-switch-power/vimar") - }, - can_handle = can_handle_zigbee_switch_power + sub_drivers = require("zigbee-switch-power.sub_drivers"), + can_handle = require("zigbee-switch-power.can_handle"), } return zigbee_switch_power diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/sub_drivers.lua new file mode 100644 index 0000000000..340e1f27c6 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/sub_drivers.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load = require "lazy_load_subdriver" + +return { + lazy_load("zigbee-switch-power.aurora-relay"), + lazy_load("zigbee-switch-power.vimar") +} diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/vimar/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/vimar/can_handle.lua new file mode 100644 index 0000000000..8143fb6a8e --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/vimar/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +return function(opts, driver, device) + local VIMAR_FINGERPRINTS = { + { mfr = "Vimar", model = "Mains_Power_Outlet_v1.0" } + } + for _, fingerprint in ipairs(VIMAR_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("zigbee-switch-power.vimar") + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/vimar/init.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/vimar/init.lua index ed65ce785b..82eb0674d4 100644 --- a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/vimar/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/vimar/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local constants = require "st.zigbee.constants" local device_management = require "st.zigbee.device_management" @@ -18,19 +7,6 @@ local zcl_clusters = require "st.zigbee.zcl.clusters" local ElectricalMeasurement = zcl_clusters.ElectricalMeasurement -local VIMAR_FINGERPRINTS = { - { mfr = "Vimar", model = "Mains_Power_Outlet_v1.0" } -} - -local function can_handle_vimar_switch_power(opts, driver, device) - for _, fingerprint in ipairs(VIMAR_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end - end - return false -end - local function do_configure(driver, device) device:configure() device:set_field(constants.SIMPLE_METERING_DIVISOR_KEY, 1, {persist = true}) @@ -45,7 +21,7 @@ local vimar_switch_power = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_vimar_switch_power + can_handle = require("zigbee-switch-power.vimar.can_handle"), } return vimar_switch_power diff --git a/drivers/SmartThings/zigbee-switch/src/zll-dimmer-bulb/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/zll-dimmer-bulb/can_handle.lua new file mode 100644 index 0000000000..f2de6f2d81 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zll-dimmer-bulb/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local ZLL_DIMMER_BULB_FINGERPRINTS = require "zll-dimmer-bulb.fingerprints" + local can_handle = (ZLL_DIMMER_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()] + if can_handle then + local subdriver = require("zll-dimmer-bulb") + return true, subdriver + else + return false + end +end diff --git a/drivers/SmartThings/zigbee-switch/src/zll-dimmer-bulb/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/zll-dimmer-bulb/fingerprints.lua new file mode 100644 index 0000000000..ab13464d62 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zll-dimmer-bulb/fingerprints.lua @@ -0,0 +1,117 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + ["AduroSmart Eria"] = { + ["ZLL-DimmableLight"] = true, + ["ZLL-ExtendedColor"] = true, + ["ZLL-ColorTemperature"] = true + }, + ["IKEA of Sweden"] = { + ["TRADFRI bulb E26 opal 1000lm"] = true, + ["TRADFRI bulb E12 W op/ch 400lm"] = true, + ["TRADFRI bulb E17 W op/ch 400lm"] = true, + ["TRADFRI bulb GU10 W 400lm"] = true, + ["TRADFRI bulb E27 W opal 1000lm"] = true, + ["TRADFRI bulb E26 W opal 1000lm"] = true, + ["TRADFRI bulb E14 W op/ch 400lm"] = true, + ["TRADFRI transformer 10W"] = true, + ["TRADFRI Driver 10W"] = true, + ["TRADFRI transformer 30W"] = true, + ["TRADFRI Driver 30W"] = true, + ["TRADFRI bulb E26 WS clear 950lm"] = true, + ["TRADFRI bulb GU10 WS 400lm"] = true, + ["TRADFRI bulb E12 WS opal 400lm"] = true, + ["TRADFRI bulb E26 WS opal 980lm"] = true, + ["TRADFRI bulb E27 WS clear 950lm"] = true, + ["TRADFRI bulb E14 WS opal 400lm"] = true, + ["TRADFRI bulb E27 WS opal 980lm"] = true, + ["FLOALT panel WS 30x30"] = true, + ["FLOALT panel WS 30x90"] = true, + ["FLOALT panel WS 60x60"] = true, + ["SURTE door WS 38x64"] = true, + ["JORMLIEN door WS 40x80"] = true, + ["TRADFRI bulb E27 CWS opal 600lm"] = true, + ["TRADFRI bulb E26 CWS opal 600lm"] = true + }, + ["Eaton"] = { + ["Halo_RL5601"] = true + }, + ["Megaman"] = { + ["ZLL-DimmableLight"] = true, + ["ZLL-ExtendedColor"] = true + }, + ["MEGAMAN"] = { + ["BSZTM002"] = true, + ["BSZTM003"] = true + }, + ["innr"] = { + ["RS 125"] = true, + ["RB 165"] = true, + ["RB 175 W"] = true, + ["RB 145"] = true, + ["RS 128 T"] = true, + ["RB 178 T"] = true, + ["RB 148 T"] = true, + ["RB 185 C"] = true, + ["FL 130 C"] = true, + ["OFL 120 C"] = true, + ["OFL 140 C"] = true, + ["OSL 130 C"] = true + }, + ["Leviton"] = { + ["DG3HL"] = true, + ["DG6HD"] = true + }, + ["OSRAM"] = { + ["Classic A60 W clear"] = true, + ["Classic A60 W clear - LIGHTIFY"] = true, + ["CLA60 OFD OSRAM"] = true, + ["Classic A60 RGBW"] = true, + ["PAR 16 50 RGBW - LIGHTIFY"] = true, + ["CLA60 RGBW OSRAM"] = true, + ["Flex RGBW"] = true, + ["Gardenpole RGBW-Lightify"] = true, + ["LIGHTIFY Outdoor Flex RGBW"] = true, + ["LIGHTIFY Indoor Flex RGBW"] = true, + ["Classic B40 TW - LIGHTIFY"] = true, + ["CLA60 TW OSRAM"] = true + }, + ["Philips"] = { + ["LWB006"] = true, + ["LWB007"] = true, + ["LWB010"] = true, + ["LWB014"] = true, + ["LCT001"] = true, + ["LCT002"] = true, + ["LCT003"] = true, + ["LCT007"] = true, + ["LCT010"] = true, + ["LCT011"] = true, + ["LCT012"] = true, + ["LCT014"] = true, + ["LCT015"] = true, + ["LCT016"] = true, + ["LST001"] = true, + ["LST002"] = true, + ["LTW001"] = true, + ["LTW004"] = true, + ["LTW010"] = true, + ["LTW011"] = true, + ["LTW012"] = true, + ["LTW013"] = true, + ["LTW014"] = true, + ["LTW015"] = true + }, + ["sengled"] = { + ["E14-U43"] = true, + ["E13-N11"] = true + }, + ["GLEDOPTO"] = { + ["GL-C-008"] = true, + ["GL-B-001Z"] = true + }, + ["Ubec"] = { + ["BBB65L-HY"] = true + } +} diff --git a/drivers/SmartThings/zigbee-switch/src/zll-dimmer-bulb/init.lua b/drivers/SmartThings/zigbee-switch/src/zll-dimmer-bulb/init.lua index 0f35666fe0..d6becf1467 100644 --- a/drivers/SmartThings/zigbee-switch/src/zll-dimmer-bulb/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zll-dimmer-bulb/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -19,131 +8,6 @@ local colorTemperature_defaults = require "st.zigbee.defaults.colorTemperature_d local OnOff = clusters.OnOff local Level = clusters.Level -local ZLL_DIMMER_BULB_FINGERPRINTS = { - ["AduroSmart Eria"] = { - ["ZLL-DimmableLight"] = true, - ["ZLL-ExtendedColor"] = true, - ["ZLL-ColorTemperature"] = true - }, - ["IKEA of Sweden"] = { - ["TRADFRI bulb E26 opal 1000lm"] = true, - ["TRADFRI bulb E12 W op/ch 400lm"] = true, - ["TRADFRI bulb E17 W op/ch 400lm"] = true, - ["TRADFRI bulb GU10 W 400lm"] = true, - ["TRADFRI bulb E27 W opal 1000lm"] = true, - ["TRADFRI bulb E26 W opal 1000lm"] = true, - ["TRADFRI bulb E14 W op/ch 400lm"] = true, - ["TRADFRI transformer 10W"] = true, - ["TRADFRI Driver 10W"] = true, - ["TRADFRI transformer 30W"] = true, - ["TRADFRI Driver 30W"] = true, - ["TRADFRI bulb E26 WS clear 950lm"] = true, - ["TRADFRI bulb GU10 WS 400lm"] = true, - ["TRADFRI bulb E12 WS opal 400lm"] = true, - ["TRADFRI bulb E26 WS opal 980lm"] = true, - ["TRADFRI bulb E27 WS clear 950lm"] = true, - ["TRADFRI bulb E14 WS opal 400lm"] = true, - ["TRADFRI bulb E27 WS opal 980lm"] = true, - ["FLOALT panel WS 30x30"] = true, - ["FLOALT panel WS 30x90"] = true, - ["FLOALT panel WS 60x60"] = true, - ["SURTE door WS 38x64"] = true, - ["JORMLIEN door WS 40x80"] = true, - ["TRADFRI bulb E27 CWS opal 600lm"] = true, - ["TRADFRI bulb E26 CWS opal 600lm"] = true - }, - ["Eaton"] = { - ["Halo_RL5601"] = true - }, - ["Megaman"] = { - ["ZLL-DimmableLight"] = true, - ["ZLL-ExtendedColor"] = true - }, - ["MEGAMAN"] = { - ["BSZTM002"] = true, - ["BSZTM003"] = true - }, - ["innr"] = { - ["RS 125"] = true, - ["RB 165"] = true, - ["RB 175 W"] = true, - ["RB 145"] = true, - ["RS 128 T"] = true, - ["RB 178 T"] = true, - ["RB 148 T"] = true, - ["RB 185 C"] = true, - ["FL 130 C"] = true, - ["OFL 120 C"] = true, - ["OFL 140 C"] = true, - ["OSL 130 C"] = true - }, - ["Leviton"] = { - ["DG3HL"] = true, - ["DG6HD"] = true - }, - ["OSRAM"] = { - ["Classic A60 W clear"] = true, - ["Classic A60 W clear - LIGHTIFY"] = true, - ["CLA60 OFD OSRAM"] = true, - ["Classic A60 RGBW"] = true, - ["PAR 16 50 RGBW - LIGHTIFY"] = true, - ["CLA60 RGBW OSRAM"] = true, - ["Flex RGBW"] = true, - ["Gardenpole RGBW-Lightify"] = true, - ["LIGHTIFY Outdoor Flex RGBW"] = true, - ["LIGHTIFY Indoor Flex RGBW"] = true, - ["Classic B40 TW - LIGHTIFY"] = true, - ["CLA60 TW OSRAM"] = true - }, - ["Philips"] = { - ["LWB006"] = true, - ["LWB007"] = true, - ["LWB010"] = true, - ["LWB014"] = true, - ["LCT001"] = true, - ["LCT002"] = true, - ["LCT003"] = true, - ["LCT007"] = true, - ["LCT010"] = true, - ["LCT011"] = true, - ["LCT012"] = true, - ["LCT014"] = true, - ["LCT015"] = true, - ["LCT016"] = true, - ["LST001"] = true, - ["LST002"] = true, - ["LTW001"] = true, - ["LTW004"] = true, - ["LTW010"] = true, - ["LTW011"] = true, - ["LTW012"] = true, - ["LTW013"] = true, - ["LTW014"] = true, - ["LTW015"] = true - }, - ["sengled"] = { - ["E14-U43"] = true, - ["E13-N11"] = true - }, - ["GLEDOPTO"] = { - ["GL-C-008"] = true, - ["GL-B-001Z"] = true - }, - ["Ubec"] = { - ["BBB65L-HY"] = true - } -} - -local function can_handle_zll_dimmer_bulb(opts, driver, device) - local can_handle = (ZLL_DIMMER_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()] - if can_handle then - local subdriver = require("zll-dimmer-bulb") - return true, subdriver - else - return false - end -end - local function do_configure(driver, device) device:configure() end @@ -204,7 +68,7 @@ local zll_dimmer_bulb = { [capabilities.colorTemperature.commands.setColorTemperature.NAME] = handle_set_color_temperature } }, - can_handle = can_handle_zll_dimmer_bulb + can_handle = require("zll-dimmer-bulb.can_handle"), } return zll_dimmer_bulb diff --git a/drivers/SmartThings/zigbee-switch/src/zll-polling/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/zll-polling/can_handle.lua new file mode 100644 index 0000000000..c39ee387e3 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/zll-polling/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, zb_rx, ...) + local constants = require "st.zigbee.constants" + + local endpoint = device.zigbee_endpoints[device.fingerprinted_endpoint_id] or device.zigbee_endpoints[tostring(device.fingerprinted_endpoint_id)] + if (endpoint ~= nil and endpoint.profile_id == constants.ZLL_PROFILE_ID) then + local subdriver = require("zll-polling") + return true, subdriver + else + return false + end +end diff --git a/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua b/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua index 1fa972ca98..3478b39bd5 100644 --- a/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua @@ -1,30 +1,8 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local device_lib = require "st.device" local clusters = require "st.zigbee.zcl.clusters" -local ZLL_PROFILE_ID = 0xC05E - -local function zll_profile(opts, driver, device, zb_rx, ...) - local endpoint = device.zigbee_endpoints[device.fingerprinted_endpoint_id] or device.zigbee_endpoints[tostring(device.fingerprinted_endpoint_id)] - if (endpoint ~= nil and endpoint.profile_id == ZLL_PROFILE_ID) then - local subdriver = require("zll-polling") - return true, subdriver - else - return false - end -end local function set_up_zll_polling(driver, device) local INFREQUENT_POLL_COUNTER = "_infrequent_poll_counter" @@ -57,7 +35,7 @@ local ZLL_polling = { lifecycle_handlers = { init = set_up_zll_polling }, - can_handle = zll_profile + can_handle = require("zll-polling.can_handle"), } -return ZLL_polling \ No newline at end of file +return ZLL_polling diff --git a/drivers/SmartThings/zigbee-thermostat/fingerprints.yml b/drivers/SmartThings/zigbee-thermostat/fingerprints.yml index 1dc993ea26..d88b3ff89a 100644 --- a/drivers/SmartThings/zigbee-thermostat/fingerprints.yml +++ b/drivers/SmartThings/zigbee-thermostat/fingerprints.yml @@ -124,6 +124,11 @@ zigbeeManufacturer: manufacturer: Resideo Korea model: DT300ST-M000 deviceProfileName: thermostat-resideo-dt300st-m000 + - id: "Resideo Korea/MC200ST" + deviceLabel: Valve Controller + manufacturer: Resideo Korea + model: MC200ST + deviceProfileName: thermostat-resideo-dt300st-m000 - id: "LUMI/lumi.airrtc.agl001" deviceLabel: Aqara Smart Radiator Thermostat E1 manufacturer: LUMI diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/aqara/can_handle.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/aqara/can_handle.lua new file mode 100644 index 0000000000..e4453597ed --- /dev/null +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/aqara/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_aqara_products(opts, driver, device) + local FINGERPRINTS = require("aqara.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("aqara") + end + end + return false +end + +return is_aqara_products diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/aqara/fingerprints.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/aqara/fingerprints.lua new file mode 100644 index 0000000000..f0ba630b6f --- /dev/null +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/aqara/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FINGERPRINTS = { + { mfr = "LUMI", model = "lumi.flood.agl02" } +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/aqara/init.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/aqara/init.lua index 7319bae636..595a10f64e 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/aqara/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" @@ -10,9 +13,6 @@ local MFG_CODE = 0x115F local PRIVATE_CLUSTER_ID = 0xFCC0 local PRIVATE_ATTRIBUTE_ID = 0x0009 -local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.flood.agl02" } -} local CONFIGURATIONS = { { @@ -25,14 +25,6 @@ local CONFIGURATIONS = { } } -local function is_aqara_products(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(driver, device) device:emit_event(capabilities.waterSensor.water.dry()) @@ -56,7 +48,7 @@ local aqara_contact_handler = { init = device_init, added = device_added }, - can_handle = is_aqara_products + can_handle = require("aqara.can_handle"), } return aqara_contact_handler diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/configurations.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/configurations.lua index a5086d979e..92b7ef226b 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/configurations.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/configurations.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local clusters = require "st.zigbee.zcl.clusters" local PowerConfiguration = clusters.PowerConfiguration diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/frient/can_handle.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/frient/can_handle.lua new file mode 100644 index 0000000000..e7c5dd9c23 --- /dev/null +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/frient/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function frient_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "frient A/S" and device:get_model() == "FLSZB-110" then + return true, require("frient") + end + return false +end + +return frient_can_handle diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/frient/init.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/frient/init.lua index 5e92ef80d6..4357eb91b2 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/frient/init.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/frient/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local clusters = require "st.zigbee.zcl.clusters" local TemperatureMeasurement = clusters.TemperatureMeasurement @@ -43,9 +33,7 @@ local frient_water_leak_sensor = { init = device_init, doConfigure = do_configure }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "frient A/S" and device:get_model() == "FLSZB-110" - end + can_handle = require("frient.can_handle"), } return frient_water_leak_sensor diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/init.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/init.lua index 68a218a586..4ded4e195c 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local ZigbeeDriver = require "st.zigbee" @@ -89,15 +79,7 @@ local zigbee_water_driver_template = { added = added_handler }, ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, - sub_drivers = { - require("aqara"), - require("zigbee-water-freeze"), - require("leaksmart"), - require("frient"), - require("thirdreality"), - require("sengled"), - require("sinope") - }, + sub_drivers = require("sub_drivers"), health_check = false, } diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/lazy_load_subdriver.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(sub_driver_name) + -- gets the current lua libs api version + local version = require "version" + local ZigbeeDriver = require "st.zigbee" + if version.api >= 16 then + return ZigbeeDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZigbeeDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end +end diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/leaksmart/can_handle.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/leaksmart/can_handle.lua new file mode 100644 index 0000000000..67c6f425ca --- /dev/null +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/leaksmart/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function leaksmart_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "WAXMAN" and device:get_model() == "leakSMART Water Sensor V2" then + return true, require("leaksmart") + end + return false +end + +return leaksmart_can_handle diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/leaksmart/init.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/leaksmart/init.lua index 010379df0f..a8ab5212b5 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/leaksmart/init.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/leaksmart/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" @@ -58,9 +48,7 @@ local leaksmart_water_sensor = { } } }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "WAXMAN" and device:get_model() == "leakSMART Water Sensor V2" - end + can_handle = require("leaksmart.can_handle"), } return leaksmart_water_sensor diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/sengled/can_handle.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/sengled/can_handle.lua new file mode 100644 index 0000000000..68d774fb8f --- /dev/null +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/sengled/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_sengled_products = function(opts, driver, device, ...) + local FINGERPRINTS = require("sengled.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("sengled") + end + end + return false +end + +return is_sengled_products diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/sengled/fingerprints.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/sengled/fingerprints.lua new file mode 100644 index 0000000000..2c3f527da6 --- /dev/null +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/sengled/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FINGERPRINTS = { + { mfr = "sengled", model = "E1L-G7K" } +} + +return FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/sengled/init.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/sengled/init.lua index f00263e309..0010073033 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/sengled/init.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/sengled/init.lua @@ -1,12 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local clusters = require "st.zigbee.zcl.clusters" local battery_defaults = require "st.zigbee.defaults.battery_defaults" local IASZone = clusters.IASZone local PowerConfiguration = clusters.PowerConfiguration -local FINGERPRINTS = { - { mfr = "sengled", model = "E1L-G7K" } -} local CONFIGURATIONS = { { @@ -27,14 +27,6 @@ local CONFIGURATIONS = { } } -local is_sengled_products = function(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_init(driver, device) battery_defaults.build_linear_voltage_init(2.6, 3.0)(driver, device) @@ -49,7 +41,7 @@ local sengled_water_leak_sensor_handler = { lifecycle_handlers = { init = device_init }, - can_handle = is_sengled_products + can_handle = require("sengled.can_handle"), } return sengled_water_leak_sensor_handler diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/sinope/can_handle.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/sinope/can_handle.lua new file mode 100644 index 0000000000..757e3ca0db --- /dev/null +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/sinope/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local is_sinope_water_sensor = function(opts, driver, device) + local SINOPE_TECHNOLOGIES_MFR_STRING = "Sinope Technologies" + if device:get_manufacturer() == SINOPE_TECHNOLOGIES_MFR_STRING then + return true, require("sinope") + else + return false + end +end + +return is_sinope_water_sensor diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/sinope/init.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/sinope/init.lua index 65cc802697..7bf6f8be82 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/sinope/init.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/sinope/init.lua @@ -1,23 +1,11 @@ --- 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. +-- Copyright 2023 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local zcl_clusters = require "st.zigbee.zcl.clusters" local IASZone = zcl_clusters.IASZone -local SINOPE_TECHNOLOGIES_MFR_STRING = "Sinope Technologies" - local generate_event_from_zone_status = function(driver, device, zone_status, zb_rx) local event if zone_status:is_alarm1_set() then @@ -39,13 +27,6 @@ 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 is_sinope_water_sensor = function(opts, driver, device) - if device:get_manufacturer() == SINOPE_TECHNOLOGIES_MFR_STRING then - return true - else - return false - end -end local sinope_water_sensor = { NAME = "Sinope Water Leak Sensor", @@ -61,7 +42,7 @@ local sinope_water_sensor = { } } }, - can_handle = is_sinope_water_sensor + can_handle = require("sinope.can_handle"), } -return sinope_water_sensor \ No newline at end of file +return sinope_water_sensor diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/sub_drivers.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/sub_drivers.lua new file mode 100644 index 0000000000..79d572579e --- /dev/null +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/sub_drivers.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" +local sub_drivers = { + lazy_load_if_possible("aqara"), + lazy_load_if_possible("zigbee-water-freeze"), + lazy_load_if_possible("leaksmart"), + lazy_load_if_possible("frient"), + lazy_load_if_possible("thirdreality"), + lazy_load_if_possible("sengled"), + lazy_load_if_possible("sinope"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_aqara_water_leak_sensor.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_aqara_water_leak_sensor.lua index 495a8455b3..ea80228581 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_aqara_water_leak_sensor.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_aqara_water_leak_sensor.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local t_utils = require "integration_test.utils" local zigbee_test_utils = require "integration_test.zigbee_test_utils" diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_centralite_water_leak_sensor.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_centralite_water_leak_sensor.lua index 4ee674199c..b42f3484ae 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_centralite_water_leak_sensor.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_centralite_water_leak_sensor.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_frient_water_leak_sensor.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_frient_water_leak_sensor.lua index 13bdb14268..56c264a505 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_frient_water_leak_sensor.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_frient_water_leak_sensor.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_leaksmart_water.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_leaksmart_water.lua index 7b20d7c3bb..86e4aa774a 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_leaksmart_water.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_leaksmart_water.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_samjin_water_leak_sensor.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_samjin_water_leak_sensor.lua index 43543e3127..3eb7cb201a 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_samjin_water_leak_sensor.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_samjin_water_leak_sensor.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_sengled_water_leak_sensor.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_sengled_water_leak_sensor.lua index d4ad7bccff..348ab81f0e 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_sengled_water_leak_sensor.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_sengled_water_leak_sensor.lua @@ -1,16 +1,6 @@ --- 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. +-- Copyright 2023 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_sinope_zigbee_water.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_sinope_zigbee_water.lua index bc476f06bb..19e1c7053e 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_sinope_zigbee_water.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_sinope_zigbee_water.lua @@ -1,16 +1,6 @@ --- 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. +-- Copyright 2023 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_smartthings_water_leak_sensor.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_smartthings_water_leak_sensor.lua index e4ffe5567c..2b9ee2d3c1 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_smartthings_water_leak_sensor.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_smartthings_water_leak_sensor.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_thirdreality_water_leak_sensor.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_thirdreality_water_leak_sensor.lua index d32f94e255..b52884f6a3 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_thirdreality_water_leak_sensor.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_thirdreality_water_leak_sensor.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local t_utils = require "integration_test.utils" local zigbee_test_utils = require "integration_test.zigbee_test_utils" diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water.lua index ab0163ef5c..3221e4fc11 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water_freeze.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water_freeze.lua index 3186af8245..9188bbc996 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water_freeze.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water_freeze.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- Mock out globals local test = require "integration_test" diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/thirdreality/can_handle.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/thirdreality/can_handle.lua new file mode 100644 index 0000000000..044050b14f --- /dev/null +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/thirdreality/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_third_reality_water_leak_sensor(opts, driver, device) + local FINGERPRINTS = require("thirdreality.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("thirdreality") + end + end + return false +end + +return can_handle_third_reality_water_leak_sensor diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/thirdreality/fingerprints.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/thirdreality/fingerprints.lua new file mode 100644 index 0000000000..f56a027f4f --- /dev/null +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/thirdreality/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local THIRD_REALITY_WATER_LEAK_SENSOR_FINGERPRINTS = { + { mfr = "Third Reality, Inc", model = "3RWS18BZ"}, + { mfr = "THIRDREALITY", model = "3RWS18BZ"} +} + +return THIRD_REALITY_WATER_LEAK_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/thirdreality/init.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/thirdreality/init.lua index 99f14294e9..8a3819ac44 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/thirdreality/init.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/thirdreality/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local zcl_clusters = require "st.zigbee.zcl.clusters" local utils = require "st.utils" @@ -20,19 +10,7 @@ local PowerConfiguration = zcl_clusters.PowerConfiguration local APPLICATION_VERSION = "application_version" -local THIRD_REALITY_WATER_LEAK_SENSOR_FINGERPRINTS = { - { mfr = "Third Reality, Inc", model = "3RWS18BZ"}, - { mfr = "THIRDREALITY", model = "3RWS18BZ"} -} -local function can_handle_third_reality_water_leak_sensor(opts, driver, device) - for _, fingerprint in ipairs(THIRD_REALITY_WATER_LEAK_SENSOR_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(driver, device) device:set_field(APPLICATION_VERSION, 0) @@ -73,7 +51,7 @@ local third_reality_water_leak_sensor = { lifecycle_handlers = { added = device_added }, - can_handle = can_handle_third_reality_water_leak_sensor + can_handle = require("thirdreality.can_handle"), } -return third_reality_water_leak_sensor \ No newline at end of file +return third_reality_water_leak_sensor diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/zigbee-water-freeze/can_handle.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/zigbee-water-freeze/can_handle.lua new file mode 100644 index 0000000000..5850a8b11c --- /dev/null +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/zigbee-water-freeze/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function zigbee_water_freeze_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "Ecolink" and device:get_model() == "FLZB1-ECO" then + return true, require("zigbee-water-freeze") + end + return false +end + +return zigbee_water_freeze_can_handle diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/zigbee-water-freeze/init.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/zigbee-water-freeze/init.lua index 68bd2ba9e0..82aca1b710 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/zigbee-water-freeze/init.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/zigbee-water-freeze/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local device_management = require "st.zigbee.device_management" local zcl_clusters = require "st.zigbee.zcl.clusters" @@ -68,9 +58,7 @@ local zigbee_water_freeze = { init = battery_defaults.build_linear_voltage_init(2.2, 3.0), doConfigure = do_configure }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Ecolink" and device:get_model() == "FLZB1-ECO" - end + can_handle = require("zigbee-water-freeze.can_handle"), } return zigbee_water_freeze diff --git a/drivers/SmartThings/zigbee-watering-kit/src/init.lua b/drivers/SmartThings/zigbee-watering-kit/src/init.lua index f39c04beaa..7dd35e6f09 100644 --- a/drivers/SmartThings/zigbee-watering-kit/src/init.lua +++ b/drivers/SmartThings/zigbee-watering-kit/src/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local ZigbeeDriver = require "st.zigbee" local defaults = require "st.zigbee.defaults" @@ -10,9 +13,7 @@ local zigbee_water_driver_template = { capabilities.fanSpeed, capabilities.mode }, - sub_drivers = { - require("thirdreality") - }, + sub_drivers = require("sub_drivers"), health_check = false, } diff --git a/drivers/SmartThings/zigbee-watering-kit/src/lazy_load_subdriver.lua b/drivers/SmartThings/zigbee-watering-kit/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..0bee6d2a75 --- /dev/null +++ b/drivers/SmartThings/zigbee-watering-kit/src/lazy_load_subdriver.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(sub_driver_name) + -- gets the current lua libs api version + local version = require "version" + local ZigbeeDriver = require "st.zigbee" + if version.api >= 16 then + return ZigbeeDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZigbeeDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end +end diff --git a/drivers/SmartThings/zigbee-watering-kit/src/sub_drivers.lua b/drivers/SmartThings/zigbee-watering-kit/src/sub_drivers.lua new file mode 100644 index 0000000000..e33b31c978 --- /dev/null +++ b/drivers/SmartThings/zigbee-watering-kit/src/sub_drivers.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" +local sub_drivers = { + lazy_load_if_possible("thirdreality"), +} +return sub_drivers diff --git a/drivers/SmartThings/zigbee-watering-kit/src/thirdreality/can_handle.lua b/drivers/SmartThings/zigbee-watering-kit/src/thirdreality/can_handle.lua new file mode 100644 index 0000000000..12afaa7d98 --- /dev/null +++ b/drivers/SmartThings/zigbee-watering-kit/src/thirdreality/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function thirdreality_can_handle(opts, driver, device, ...) + if device:get_manufacturer() == "Third Reality, Inc" and device:get_model() == "3RWK0148Z" then + return true, require("thirdreality") + end + return false +end + +return thirdreality_can_handle diff --git a/drivers/SmartThings/zigbee-watering-kit/src/thirdreality/init.lua b/drivers/SmartThings/zigbee-watering-kit/src/thirdreality/init.lua index 5f73f3f9ca..5e36e15624 100644 --- a/drivers/SmartThings/zigbee-watering-kit/src/thirdreality/init.lua +++ b/drivers/SmartThings/zigbee-watering-kit/src/thirdreality/init.lua @@ -1,3 +1,6 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" local IASZone = clusters.IASZone @@ -97,9 +100,7 @@ local thirdreality_device_handler = { lifecycle_handlers = { added = device_added }, - can_handle = function(opts, driver, device, ...) - return device:get_manufacturer() == "Third Reality, Inc" and device:get_model() == "3RWK0148Z" - end + can_handle = require("thirdreality.can_handle"), } return thirdreality_device_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml b/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml index 808add60ff..e15dd6f3a2 100644 --- a/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml +++ b/drivers/SmartThings/zigbee-window-treatment/fingerprints.yml @@ -128,6 +128,16 @@ zigbeeManufacturer: manufacturer: VIVIDSTORM model: VWSDSTUST120H deviceProfileName: projector-screen-VWSDSTUST120H + - id: "NodOn/SIN-4-RS-20" + deviceLabel: Zigbee Roller Shutter Relay Switch + manufacturer: NodOn + model: SIN-4-RS-20 + deviceProfileName: window-treatment-profile + - id: "HOPOsmart/A2230011" + deviceLabel: HOPOsmart Window Opener A2230011 + manufacturer: HOPOsmart + model: A2230011 + deviceProfileName: window-shade-only zigbeeGeneric: - id: "genericShade" diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-shade-only.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-shade-only.yml new file mode 100755 index 0000000000..7adffe309e --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-shade-only.yml @@ -0,0 +1,12 @@ +name: window-shade-only +components: +- id: main + capabilities: + - id: windowShade + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: WindowOpener diff --git a/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/custom_clusters.lua b/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/custom_clusters.lua new file mode 100755 index 0000000000..1f2950b2f1 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/custom_clusters.lua @@ -0,0 +1,30 @@ +-- Copyright 2025 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 data_types = require "st.zigbee.data_types" + +local custom_clusters = { + motor = { + id = 0xFCC8, + mfg_specific_code = 0x1235, + attributes = { + state_value = { + id = 0x0000, + value_type = data_types.Uint8, + } + } + } +} + +return custom_clusters diff --git a/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/init.lua new file mode 100755 index 0000000000..5e3f002ec0 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/HOPOsmart/init.lua @@ -0,0 +1,89 @@ +-- Copyright 2025 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 custom_clusters = require "HOPOsmart/custom_clusters" +local cluster_base = require "st.zigbee.cluster_base" + +local ZIGBEE_WINDOW_SHADE_FINGERPRINTS = { + { mfr = "HOPOsmart", model = "A2230011" } +} + + +local is_zigbee_window_shade = function(opts, driver, device) + for _, fingerprint in ipairs(ZIGBEE_WINDOW_SHADE_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true + end + end + return false +end + +local function send_read_attr_request(device, cluster, attr) + device:send( + cluster_base.read_manufacturer_specific_attribute( + device, + cluster.id, + attr.id, + cluster.mfg_specific_code + ) + ) +end + +local function state_value_attr_handler(driver, device, value, zb_rx) + if value.value == 0 then + device:emit_event(capabilities.windowShade.windowShade.open()) + elseif value.value == 1 then + device:emit_event(capabilities.windowShade.windowShade.opening()) + elseif value.value == 2 then + device:emit_event(capabilities.windowShade.windowShade.closed()) + elseif value.value == 3 then + device:emit_event(capabilities.windowShade.windowShade.closing()) + elseif value.value == 4 then + device:emit_event(capabilities.windowShade.windowShade.partially_open()) + end +end + +local function do_refresh(driver, device) + send_read_attr_request(device, custom_clusters.motor, custom_clusters.motor.attributes.state_value) +end + +local function added_handler(self, device) + do_refresh(self, device) +end + +local HOPOsmart_handler = { + NAME = "HOPOsmart Device Handler", + supported_capabilities = { + capabilities.refresh + }, + lifecycle_handlers = { + added = added_handler + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + zigbee_handlers = { + attr = { + [custom_clusters.motor.id] = { + [custom_clusters.motor.attributes.state_value.id] = state_value_attr_handler + } + } + }, + can_handle = is_zigbee_window_shade, +} + +return HOPOsmart_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/axis/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/axis/init.lua index a47de06df7..612dd450a5 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/axis/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/axis/init.lua @@ -110,6 +110,7 @@ local do_configure = function(self, device) device:send(WindowCovering.attributes.CurrentPositionLiftPercentage:configure_reporting(device, 1, 3600, 1)) device:send(device_management.build_bind_request(device, PowerConfiguration.ID, self.environment_info.hub_zigbee_eui)) device:send(PowerConfiguration.attributes.BatteryPercentageRemaining:configure_reporting(device, 1, 3600, 1)) + device:refresh() end local device_added = function(self, device) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/feibit/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/feibit/init.lua index f33bf81cbd..e0fd17219e 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/feibit/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/feibit/init.lua @@ -61,6 +61,7 @@ end local do_configure = function(self, device) device:send(device_management.build_bind_request(device, Level.ID, self.environment_info.hub_zigbee_eui)) device:send(Level.attributes.CurrentLevel:configure_reporting(device, 1, 3600, 1)) + device:refresh() end local feibit_handler = { diff --git a/drivers/SmartThings/zigbee-window-treatment/src/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/init.lua index 246d65f463..3403b3d528 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/init.lua @@ -57,7 +57,8 @@ local zigbee_window_treatment_driver_template = { require("yoolax"), require("hanssem"), require("screen-innovations"), - require("VIVIDSTORM")}, + require("VIVIDSTORM"), + require("HOPOsmart")}, lifecycle_handlers = { init = init_handler, added = added_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_only_HOPOsmart.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_only_HOPOsmart.lua new file mode 100755 index 0000000000..1e4a8b3a2a --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_only_HOPOsmart.lua @@ -0,0 +1,198 @@ +-- Copyright 2025 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. + +-- Mock out globals +local test = require "integration_test" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local data_types = require "st.zigbee.data_types" +local cluster_base = require "st.zigbee.cluster_base" + +local PRIVATE_CLUSTER_ID = 0xFCC8 +local MFG_CODE = 0x1235 + +local mock_device = test.mock_device.build_test_zigbee_device( + { profile = t_utils.get_profile_definition("window-shade-only.yml"), + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "HOPOsmart", + model = "A2230011", + server_clusters = {0x0000, 0xFCC8} + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.mock_device.add_test_device(mock_device) + zigbee_test_utils.init_noop_health_check_timer() +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "lifecycle - added test", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + local read_0x0000_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0000_messge}) + end +) + +test.register_message_test( + "Handle Window shade open command", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { + capability = "windowShade", component = "main", command = "open", args = {} + } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, clusters.WindowCovering.server.commands.UpOrOpen(mock_device) } + } + } +) + +test.register_message_test( + "Handle Window shade close command", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { + capability = "windowShade", component = "main", command = "close", args = {} + } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + clusters.WindowCovering.server.commands.DownOrClose(mock_device) + } + } + } +) + +test.register_message_test( + "Handle Window shade pause command", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "windowShade", component = "main", command = "pause", args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + clusters.WindowCovering.server.commands.Stop(mock_device) + } + } + } +) + +test.register_coroutine_test( + "Device reported 0 and driver emit windowShade.open", + function() + local attr_report_data = { + { 0x0000, data_types.Uint8.ID, 0 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.windowShade.windowShade.open())) + end +) + +test.register_coroutine_test( + "Device reported 1 and driver emit windowShade.opening", + function() + local attr_report_data = { + { 0x0000, data_types.Uint8.ID, 1 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.windowShade.windowShade.opening())) + end +) + +test.register_coroutine_test( + "Device reported 2 and driver emit windowShade.closed", + function() + local attr_report_data = { + { 0x0000, data_types.Uint8.ID, 2 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.windowShade.windowShade.closed())) + end +) + +test.register_coroutine_test( + "Device reported 3 and driver emit windowShade.closeing", + function() + local attr_report_data = { + { 0x0000, data_types.Uint8.ID, 3 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.windowShade.windowShade.closing())) + end +) + +test.register_coroutine_test( + "Device reported 4 and driver emit windowShade.partially_open", + function() + local attr_report_data = { + { 0x0000, data_types.Uint8.ID, 4 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.windowShade.windowShade.partially_open())) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_axis.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_axis.lua index 5068f889a5..f3564ff79f 100755 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_axis.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_axis.lua @@ -637,6 +637,8 @@ test.register_coroutine_test( zigbee_test_utils.mock_hub_eui, PowerConfiguration.ID) }) + test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) }) end ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_feibit.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_feibit.lua index c65d38281a..aa0480eb2a 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_feibit.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_feibit.lua @@ -20,6 +20,7 @@ local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local Level = clusters.Level +local WindowCovering = clusters.WindowCovering local mock_device = test.mock_device.build_test_zigbee_device( { profile = t_utils.get_profile_definition("window-treatment-profile.yml"), @@ -396,6 +397,7 @@ test.register_coroutine_test( zigbee_test_utils.mock_hub_eui, Level.ID) }) + test.socket.zigbee:__expect_send({ mock_device.id, WindowCovering.attributes.CurrentPositionLiftPercentage:read(mock_device) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_hanssem.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_hanssem.lua index 23814eff28..2e511c99bb 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_hanssem.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_hanssem.lua @@ -88,6 +88,7 @@ end test.register_coroutine_test( "Device Added ", function() + SeqNum = 0 test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.socket.capability:__expect_send( @@ -106,6 +107,7 @@ test.register_coroutine_test( test.register_coroutine_test( "Open handler", function() + SeqNum = 0 test.socket.zigbee:__queue_receive({ mock_device.id, build_rx_message(mock_device,"\x03\x02\x00\x04\x00\x00\x00\x00") @@ -147,6 +149,7 @@ test.register_coroutine_test( test.register_coroutine_test( "Close handler", function() + SeqNum = 0 test.socket.zigbee:__queue_receive({ mock_device.id, build_rx_message(mock_device,"\x03\x02\x00\x04\x00\x00\x00\x64") @@ -189,6 +192,7 @@ test.register_coroutine_test( test.register_coroutine_test( "Pause handler", function() + SeqNum = 0 test.socket.zigbee:__queue_receive({ mock_device.id, build_rx_message(mock_device,"\x03\x02\x00\x04\x00\x00\x00\x64") @@ -243,6 +247,7 @@ test.register_coroutine_test( test.register_coroutine_test( "Set Level handler", function() + SeqNum = 0 test.socket.zigbee:__queue_receive({ mock_device.id, build_rx_message(mock_device,"\x03\x02\x00\x04\x00\x00\x00\x64") @@ -285,6 +290,7 @@ test.register_coroutine_test( test.register_coroutine_test( "Preset position handler", function() + SeqNum = 0 test.socket.capability:__queue_receive( { mock_device.id, @@ -336,6 +342,7 @@ test.register_coroutine_test( test.register_coroutine_test( "Information changed : Reverse", function() + SeqNum = 0 test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") test.socket.zigbee:__queue_receive({ mock_device.id, diff --git a/drivers/SmartThings/zwave-bulb/src/aeon-multiwhite-bulb/can_handle.lua b/drivers/SmartThings/zwave-bulb/src/aeon-multiwhite-bulb/can_handle.lua new file mode 100644 index 0000000000..6ff48d2fe2 --- /dev/null +++ b/drivers/SmartThings/zwave-bulb/src/aeon-multiwhite-bulb/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_aeon_multiwhite_bulb(opts, driver, device, ...) + local FINGERPRINTS = require("aeon-multiwhite-bulb.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("aeon-multiwhite-bulb") + end + end + return false +end + +return can_handle_aeon_multiwhite_bulb diff --git a/drivers/SmartThings/zwave-bulb/src/aeon-multiwhite-bulb/fingerprints.lua b/drivers/SmartThings/zwave-bulb/src/aeon-multiwhite-bulb/fingerprints.lua new file mode 100644 index 0000000000..6ec474efa9 --- /dev/null +++ b/drivers/SmartThings/zwave-bulb/src/aeon-multiwhite-bulb/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEON_MULTIWHITE_BULB_FINGERPRINTS = { + {mfr = 0x0371, prod = 0x0103, model = 0x0001}, -- Aeon LED Bulb 6 Multi-White US + {mfr = 0x0371, prod = 0x0003, model = 0x0001}, -- Aeon LED Bulb 6 Multi-White EU + {mfr = 0x0300, prod = 0x0003, model = 0x0004} -- ilumin Tunable White +} + +return AEON_MULTIWHITE_BULB_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-bulb/src/aeon-multiwhite-bulb/init.lua b/drivers/SmartThings/zwave-bulb/src/aeon-multiwhite-bulb/init.lua index 9907fefbb5..4a9cc277ca 100644 --- a/drivers/SmartThings/zwave-bulb/src/aeon-multiwhite-bulb/init.lua +++ b/drivers/SmartThings/zwave-bulb/src/aeon-multiwhite-bulb/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.utils @@ -26,25 +16,12 @@ local SwitchColor = (require "st.zwave.CommandClass.SwitchColor")({ version = 3 --- @type st.zwave.CommandClass.SwitchMultilevel local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({ version = 4 }) -local AEON_MULTIWHITE_BULB_FINGERPRINTS = { - {mfr = 0x0371, prod = 0x0103, model = 0x0001}, -- Aeon LED Bulb 6 Multi-White US - {mfr = 0x0371, prod = 0x0003, model = 0x0001}, -- Aeon LED Bulb 6 Multi-White EU - {mfr = 0x0300, prod = 0x0003, model = 0x0004} -- ilumin Tunable White -} local WARM_WHITE_CONFIG = 0x51 local COLD_WHITE_CONFIG = 0x52 local SWITCH_COLOR_QUERY_DELAY = 2 local DEFAULT_COLOR_TEMPERATURE = 2700 -local function can_handle_aeon_multiwhite_bulb(opts, driver, device, ...) - for _, fingerprint in ipairs(AEON_MULTIWHITE_BULB_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end local function onoff_level_report_handler(self, device, cmd) local value = cmd.args.target_value and cmd.args.target_value or cmd.args.value @@ -126,7 +103,7 @@ local aeon_multiwhite_bulb = { [capabilities.colorTemperature.commands.setColorTemperature.NAME] = set_color_temperature } }, - can_handle = can_handle_aeon_multiwhite_bulb, + can_handle = require("aeon-multiwhite-bulb.can_handle"), lifecycle_handlers = { added = device_added } diff --git a/drivers/SmartThings/zwave-bulb/src/aeotec-led-bulb-6/can_handle.lua b/drivers/SmartThings/zwave-bulb/src/aeotec-led-bulb-6/can_handle.lua new file mode 100644 index 0000000000..ebeafc2c5c --- /dev/null +++ b/drivers/SmartThings/zwave-bulb/src/aeotec-led-bulb-6/can_handle.lua @@ -0,0 +1,23 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +--- Determine whether the passed device is an Aeotec LED Bulb 6. +--- +--- @param driver Driver driver instance +--- @param device Device device isntance +--- @return boolean true if the device is an Aeotec LED Bulb 6, else false +local function is_aeotec_led_bulb_6(opts, driver, device, ...) + local AEOTEC_MFR_ID = 0x0371 + local AEOTEC_LED_BULB_6_PRODUCT_TYPE_US = 0x0103 + local AEOTEC_LED_BULB_6_PRODUCT_TYPE_EU = 0x0003 + local AEOTEC_LED_BULB_6_PRODUCT_ID = 0x0002 + if device:id_match( + AEOTEC_MFR_ID, + { AEOTEC_LED_BULB_6_PRODUCT_TYPE_US, AEOTEC_LED_BULB_6_PRODUCT_TYPE_EU }, + AEOTEC_LED_BULB_6_PRODUCT_ID) then + return true, require("aeotec-led-bulb-6") + end + return false +end + +return is_aeotec_led_bulb_6 diff --git a/drivers/SmartThings/zwave-bulb/src/aeotec-led-bulb-6/init.lua b/drivers/SmartThings/zwave-bulb/src/aeotec-led-bulb-6/init.lua index 67755878db..e4886c5897 100644 --- a/drivers/SmartThings/zwave-bulb/src/aeotec-led-bulb-6/init.lua +++ b/drivers/SmartThings/zwave-bulb/src/aeotec-led-bulb-6/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.utils @@ -26,10 +16,6 @@ local SwitchColor = (require "st.zwave.CommandClass.SwitchColor")({ version=3 }) --- @type st.zwave.CommandClass.SwitchMultilevel local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({ version=4 }) -local AEOTEC_MFR_ID = 0x0371 -local AEOTEC_LED_BULB_6_PRODUCT_TYPE_US = 0x0103 -local AEOTEC_LED_BULB_6_PRODUCT_TYPE_EU = 0x0003 -local AEOTEC_LED_BULB_6_PRODUCT_ID = 0x0002 local WARM_WHITE_CONFIG = 0x51 local COLD_WHITE_CONFIG = 0x52 @@ -102,18 +88,6 @@ function capability_handlers.refresh(driver, device) device:send(Configuration:Get({ parameter_number=COLD_WHITE_CONFIG })) end ---- Determine whether the passed device is an Aeotec LED Bulb 6. ---- ---- @param driver Driver driver instance ---- @param device Device device isntance ---- @return boolean true if the device is an Aeotec LED Bulb 6, else false -local function is_aeotec_led_bulb_6(opts, driver, device, ...) - return device:id_match( - AEOTEC_MFR_ID, - { AEOTEC_LED_BULB_6_PRODUCT_TYPE_US, AEOTEC_LED_BULB_6_PRODUCT_TYPE_EU }, - AEOTEC_LED_BULB_6_PRODUCT_ID) -end - local aeotec_led_bulb_6 = { NAME = "Aeotec LED Bulb 6", zwave_handlers = { @@ -129,7 +103,7 @@ local aeotec_led_bulb_6 = { [capabilities.refresh.commands.refresh.NAME] = capability_handlers.refresh } }, - can_handle = is_aeotec_led_bulb_6, + can_handle = require("aeotec-led-bulb-6.can_handle"), } return aeotec_led_bulb_6 diff --git a/drivers/SmartThings/zwave-bulb/src/fibaro-rgbw-controller/can_handle.lua b/drivers/SmartThings/zwave-bulb/src/fibaro-rgbw-controller/can_handle.lua new file mode 100644 index 0000000000..aeef24b0ea --- /dev/null +++ b/drivers/SmartThings/zwave-bulb/src/fibaro-rgbw-controller/can_handle.lua @@ -0,0 +1,20 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function is_fibaro_rgbw_controller(opts, driver, device, ...) + local FIBARO_MFR_ID = 0x010F + local FIBARO_RGBW_CONTROLLER_PROD_TYPE = 0x0900 + local FIBARO_RGBW_CONTROLLER_PROD_ID_US = 0x2000 + local FIBARO_RGBW_CONTROLLER_PROD_ID_EU = 0x1000 + + if device:id_match( + FIBARO_MFR_ID, + FIBARO_RGBW_CONTROLLER_PROD_TYPE, + {FIBARO_RGBW_CONTROLLER_PROD_ID_US, FIBARO_RGBW_CONTROLLER_PROD_ID_EU} + ) then + return true, require("fibaro-rgbw-controller") + end + return false +end + +return is_fibaro_rgbw_controller diff --git a/drivers/SmartThings/zwave-bulb/src/fibaro-rgbw-controller/init.lua b/drivers/SmartThings/zwave-bulb/src/fibaro-rgbw-controller/init.lua index 80dc8d4dc7..b83be97c23 100644 --- a/drivers/SmartThings/zwave-bulb/src/fibaro-rgbw-controller/init.lua +++ b/drivers/SmartThings/zwave-bulb/src/fibaro-rgbw-controller/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass.Association @@ -33,19 +23,6 @@ local CAP_CACHE_KEY = "st.capabilities." .. capabilities.colorControl.ID local LAST_COLOR_SWITCH_CMD_FIELD = "lastColorSwitchCmd" local FAKE_RGB_ENDPOINT = 10 -local FIBARO_MFR_ID = 0x010F -local FIBARO_RGBW_CONTROLLER_PROD_TYPE = 0x0900 -local FIBARO_RGBW_CONTROLLER_PROD_ID_US = 0x2000 -local FIBARO_RGBW_CONTROLLER_PROD_ID_EU = 0x1000 - -local function is_fibaro_rgbw_controller(opts, driver, device, ...) - return device:id_match( - FIBARO_MFR_ID, - FIBARO_RGBW_CONTROLLER_PROD_TYPE, - {FIBARO_RGBW_CONTROLLER_PROD_ID_US, FIBARO_RGBW_CONTROLLER_PROD_ID_EU} - ) -end - -- This handler is copied from defaults with scraped of sets for both WHITE channels local function set_color(driver, device, command) local r, g, b = utils.hsl_to_rgb(command.args.color.hue, command.args.color.saturation, command.args.color.lightness) @@ -201,7 +178,7 @@ local fibaro_rgbw_controller = { added = device_added, init = device_init }, - can_handle = is_fibaro_rgbw_controller, + can_handle = require("fibaro-rgbw-controller.can_handle"), } return fibaro_rgbw_controller diff --git a/drivers/SmartThings/zwave-bulb/src/init.lua b/drivers/SmartThings/zwave-bulb/src/init.lua index 62079594fa..58e2f32a7e 100644 --- a/drivers/SmartThings/zwave-bulb/src/init.lua +++ b/drivers/SmartThings/zwave-bulb/src/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.Driver @@ -30,11 +20,7 @@ local driver_template = { capabilities.colorTemperature, capabilities.powerMeter }, - sub_drivers = { - require("aeotec-led-bulb-6"), - require("aeon-multiwhite-bulb"), - require("fibaro-rgbw-controller") - }, + sub_drivers = require("sub_drivers"), } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities, {native_capability_cmds_enabled = true}) diff --git a/drivers/SmartThings/zwave-bulb/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-bulb/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..45115081e4 --- /dev/null +++ b/drivers/SmartThings/zwave-bulb/src/lazy_load_subdriver.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +return function(sub_driver_name) + -- gets the current lua libs api version + local ZwaveDriver = require "st.zwave.driver" + local version = require "version" + + if version.api >= 16 then + return ZwaveDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZwaveDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end + +end diff --git a/drivers/SmartThings/zwave-bulb/src/sub_drivers.lua b/drivers/SmartThings/zwave-bulb/src/sub_drivers.lua new file mode 100644 index 0000000000..842e2056bd --- /dev/null +++ b/drivers/SmartThings/zwave-bulb/src/sub_drivers.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" +local sub_drivers = { + lazy_load_if_possible("aeotec-led-bulb-6"), + lazy_load_if_possible("aeon-multiwhite-bulb"), + lazy_load_if_possible("fibaro-rgbw-controller"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-bulb/src/test/test_aeon_multiwhite_bulb.lua b/drivers/SmartThings/zwave-bulb/src/test/test_aeon_multiwhite_bulb.lua index d6cdc044e0..7ae91bc853 100644 --- a/drivers/SmartThings/zwave-bulb/src/test/test_aeon_multiwhite_bulb.lua +++ b/drivers/SmartThings/zwave-bulb/src/test/test_aeon_multiwhite_bulb.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-bulb/src/test/test_aeotec_led_bulb_6.lua b/drivers/SmartThings/zwave-bulb/src/test/test_aeotec_led_bulb_6.lua index 0a1fa7e955..8757c4e11a 100644 --- a/drivers/SmartThings/zwave-bulb/src/test/test_aeotec_led_bulb_6.lua +++ b/drivers/SmartThings/zwave-bulb/src/test/test_aeotec_led_bulb_6.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-bulb/src/test/test_fibaro_rgbw_controller.lua b/drivers/SmartThings/zwave-bulb/src/test/test_fibaro_rgbw_controller.lua index 0fd3e98995..1610440aea 100644 --- a/drivers/SmartThings/zwave-bulb/src/test/test_fibaro_rgbw_controller.lua +++ b/drivers/SmartThings/zwave-bulb/src/test/test_fibaro_rgbw_controller.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-bulb/src/test/test_zwave_bulb.lua b/drivers/SmartThings/zwave-bulb/src/test/test_zwave_bulb.lua index cf704bec2d..9d5e199927 100644 --- a/drivers/SmartThings/zwave-bulb/src/test/test_zwave_bulb.lua +++ b/drivers/SmartThings/zwave-bulb/src/test/test_zwave_bulb.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeon-meter/can_handle.lua b/drivers/SmartThings/zwave-electric-meter/src/aeon-meter/can_handle.lua new file mode 100644 index 0000000000..a0bcfde376 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeon-meter/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_aeon_meter(opts, driver, device, ...) + local FINGERPRINTS = require("aeon-meter.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("aeon-meter") + end + end + return false +end + +return can_handle_aeon_meter diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeon-meter/fingerprints.lua b/drivers/SmartThings/zwave-electric-meter/src/aeon-meter/fingerprints.lua new file mode 100644 index 0000000000..5970f40d6d --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeon-meter/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEON_FINGERPRINTS = { + {mfr = 0x0086, prod = 0x0002, model = 0x0009}, -- DSB09xxx-ZWUS + {mfr = 0x0086, prod = 0x0002, model = 0x0001}, -- DSB28-ZWEU +} + +return AEON_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeon-meter/init.lua b/drivers/SmartThings/zwave-electric-meter/src/aeon-meter/init.lua index 9f5ec4bee4..334bbb71cd 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/aeon-meter/init.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/aeon-meter/init.lua @@ -1,34 +1,9 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=1 }) -local AEON_FINGERPRINTS = { - {mfr = 0x0086, prod = 0x0002, model = 0x0009}, -- DSB09xxx-ZWUS - {mfr = 0x0086, prod = 0x0002, model = 0x0001}, -- DSB28-ZWEU -} - -local function can_handle_aeon_meter(opts, driver, device, ...) - for _, fingerprint in ipairs(AEON_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end - local do_configure = function (self, device) device:send(Configuration:Set({parameter_number = 101, size = 4, configuration_value = 4})) -- combined power in watts... device:send(Configuration:Set({parameter_number = 111, size = 4, configuration_value = 300})) -- ...every 5 min @@ -42,7 +17,7 @@ local aeon_meter = { doConfigure = do_configure }, NAME = "aeon meter", - can_handle = can_handle_aeon_meter + can_handle = require("aeon-meter.can_handle"), } return aeon_meter diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-gen5-meter/can_handle.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-gen5-meter/can_handle.lua new file mode 100644 index 0000000000..356dbd55df --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-gen5-meter/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_aeotec_gen5_meter(opts, driver, device, ...) + local FINGERPRINTS = require("aeotec-gen5-meter.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("aeotec-gen5-meter") + end + end + return false +end + +return can_handle_aeotec_gen5_meter diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-gen5-meter/fingerprints.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-gen5-meter/fingerprints.lua new file mode 100644 index 0000000000..3863c27b51 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-gen5-meter/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEOTEC_GEN5_FINGERPRINTS = { + {mfr = 0x0086, prod = 0x0102, model = 0x005F}, -- Aeotec Home Energy Meter (Gen5) US + {mfr = 0x0086, prod = 0x0002, model = 0x005F}, -- Aeotec Home Energy Meter (Gen5) EU +} + +return AEOTEC_GEN5_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-electric-meter/src/aeotec-gen5-meter/init.lua b/drivers/SmartThings/zwave-electric-meter/src/aeotec-gen5-meter/init.lua index 870e104891..1633427372 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/aeotec-gen5-meter/init.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/aeotec-gen5-meter/init.lua @@ -1,34 +1,9 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=1 }) -local AEOTEC_GEN5_FINGERPRINTS = { - {mfr = 0x0086, prod = 0x0102, model = 0x005F}, -- Aeotec Home Energy Meter (Gen5) US - {mfr = 0x0086, prod = 0x0002, model = 0x005F}, -- Aeotec Home Energy Meter (Gen5) EU -} - -local function can_handle_aeotec_gen5_meter(opts, driver, device, ...) - for _, fingerprint in ipairs(AEOTEC_GEN5_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end - local do_configure = function (self, device) device:send(Configuration:Set({parameter_number = 101, size = 4, configuration_value = 3})) -- report total power in Watts and total energy in kWh... device:send(Configuration:Set({parameter_number = 102, size = 4, configuration_value = 0})) -- disable group 2... @@ -43,7 +18,7 @@ local aeotec_gen5_meter = { doConfigure = do_configure }, NAME = "aeotec gen5 meter", - can_handle = can_handle_aeotec_gen5_meter + can_handle = require("aeotec-gen5-meter.can_handle"), } return aeotec_gen5_meter diff --git a/drivers/SmartThings/zwave-electric-meter/src/init.lua b/drivers/SmartThings/zwave-electric-meter/src/init.lua index 2ef9f20281..66205758bd 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/init.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.defaults @@ -31,11 +21,7 @@ local driver_template = { lifecycle_handlers = { added = device_added }, - sub_drivers = { - require("qubino-meter"), - require("aeotec-gen5-meter"), - require("aeon-meter") - } + sub_drivers = require("sub_drivers"), } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) diff --git a/drivers/SmartThings/zwave-electric-meter/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-electric-meter/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..45115081e4 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/lazy_load_subdriver.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +return function(sub_driver_name) + -- gets the current lua libs api version + local ZwaveDriver = require "st.zwave.driver" + local version = require "version" + + if version.api >= 16 then + return ZwaveDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZwaveDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end + +end diff --git a/drivers/SmartThings/zwave-electric-meter/src/qubino-meter/can_handle.lua b/drivers/SmartThings/zwave-electric-meter/src/qubino-meter/can_handle.lua new file mode 100644 index 0000000000..dd9ae17949 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/qubino-meter/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_qubino_meter(opts, driver, device, ...) + local FINGERPRINTS = require("qubino-meter.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("qubino-meter") + end + end + return false +end + +return can_handle_qubino_meter diff --git a/drivers/SmartThings/zwave-electric-meter/src/qubino-meter/fingerprints.lua b/drivers/SmartThings/zwave-electric-meter/src/qubino-meter/fingerprints.lua new file mode 100644 index 0000000000..5a5fd44be5 --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/qubino-meter/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local QUBINO_FINGERPRINTS = { + {mfr = 0x0159, prod = 0x0007, model = 0x0054}, -- Qubino 3 Phase Meter + {mfr = 0x0159, prod = 0x0007, model = 0x0052} -- Qubino Smart Meter +} + +return QUBINO_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-electric-meter/src/qubino-meter/init.lua b/drivers/SmartThings/zwave-electric-meter/src/qubino-meter/init.lua index a8e2b23805..4c2a40a56e 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/qubino-meter/init.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/qubino-meter/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass.Configuration @@ -20,22 +10,10 @@ local Meter = (require "st.zwave.CommandClass.Meter")({ version=3 }) --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" -local QUBINO_FINGERPRINTS = { - {mfr = 0x0159, prod = 0x0007, model = 0x0054}, -- Qubino 3 Phase Meter - {mfr = 0x0159, prod = 0x0007, model = 0x0052} -- Qubino Smart Meter -} local POWER_UNIT_WATT = "W" local ENERGY_UNIT_KWH = "kWh" -local function can_handle_qubino_meter(opts, driver, device, ...) - for _, fingerprint in ipairs(QUBINO_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end local function meter_report_handler(self, device, cmd) @@ -101,7 +79,7 @@ local qubino_meter = { init = device_init }, NAME = "qubino meter", - can_handle = can_handle_qubino_meter + can_handle = require("qubino-meter.can_handle"), } return qubino_meter diff --git a/drivers/SmartThings/zwave-electric-meter/src/sub_drivers.lua b/drivers/SmartThings/zwave-electric-meter/src/sub_drivers.lua new file mode 100644 index 0000000000..60d4a7380b --- /dev/null +++ b/drivers/SmartThings/zwave-electric-meter/src/sub_drivers.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" +local sub_drivers = { + lazy_load_if_possible("qubino-meter"), + lazy_load_if_possible("aeotec-gen5-meter"), + lazy_load_if_possible("aeon-meter"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeon_meter.lua b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeon_meter.lua index d82e1549fa..cb92a861fe 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeon_meter.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeon_meter.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_gen5_meter.lua b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_gen5_meter.lua index 09b897ba9d..29255434d1 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_gen5_meter.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/test/test_aeotec_gen5_meter.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-electric-meter/src/test/test_qubino_3_phase_meter.lua b/drivers/SmartThings/zwave-electric-meter/src/test/test_qubino_3_phase_meter.lua index dad4b1030e..55f1b9e436 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/test/test_qubino_3_phase_meter.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/test/test_qubino_3_phase_meter.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-electric-meter/src/test/test_qubino_smart_meter.lua b/drivers/SmartThings/zwave-electric-meter/src/test/test_qubino_smart_meter.lua index 11a1317a74..47391c078f 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/test/test_qubino_smart_meter.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/test/test_qubino_smart_meter.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-electric-meter/src/test/test_zwave_electric_meter.lua b/drivers/SmartThings/zwave-electric-meter/src/test/test_zwave_electric_meter.lua index cf640c3655..01c3227147 100644 --- a/drivers/SmartThings/zwave-electric-meter/src/test/test_zwave_electric_meter.lua +++ b/drivers/SmartThings/zwave-electric-meter/src/test/test_zwave_electric_meter.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-sensor/fingerprints.yml b/drivers/SmartThings/zwave-sensor/fingerprints.yml index 6551443db9..e879cc1270 100644 --- a/drivers/SmartThings/zwave-sensor/fingerprints.yml +++ b/drivers/SmartThings/zwave-sensor/fingerprints.yml @@ -502,12 +502,12 @@ zwaveManufacturer: productType: 0x4C47 productId: 0x4C44 deviceProfileName: base-water - - id: 027A/7000/E002 - deviceLabel: Zooz Water Leak Sensor + - id: "Zooz/ZSE42" + deviceLabel: Zooz ZSE42 XS Water Leak Sensor manufacturerId: 0x027A productType: 0x7000 productId: 0xE002 - deviceProfileName: base-water + deviceProfileName: zooz-zse42-water - id: 010F/0800 deviceLabel: Fibaro Motion Sensor manufacturerId: 0x010F diff --git a/drivers/SmartThings/zwave-sensor/profiles/zooz-zse42-water.yml b/drivers/SmartThings/zwave-sensor/profiles/zooz-zse42-water.yml new file mode 100644 index 0000000000..1a497e7438 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/profiles/zooz-zse42-water.yml @@ -0,0 +1,52 @@ +name: zooz-zse42-water +components: + - id: main + capabilities: + - id: waterSensor + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + - id: firmwareUpdate + version: 1 + categories: + - name: LeakSensor +preferences: + #param 1 + - name: "ledIndicator" + title: "LED Indicator" + description: "When enabled the LED indicator will blink continuously when water is detected." + required: false + preferenceType: enumeration + definition: + options: + 1: "Enabled *" + 0: "Disabled" + default: 1 + #param 2 + - name: "leakAlertClearDelay" + title: "Leak Alert Clear Delay" + description: "Default = 0; How long the sensor will wait before sending a 'dry' report to your hub after water is no longer detected." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum: 3600 + default: 0 + #param 4 + - name: "lowBatteryAlert" + title: "Low Battery Alert" + description: "Battery level threshold for low battery reports." + required: false + preferenceType: enumeration + definition: + options: + 10: "10%" + 15: "15%" + 20: "20% *" + 25: "25%" + 30: "30%" + 40: "40%" + 50: "50%" + default: 20 diff --git a/drivers/SmartThings/zwave-sensor/src/configurations.lua b/drivers/SmartThings/zwave-sensor/src/configurations.lua index 89c89428fa..2883e70384 100644 --- a/drivers/SmartThings/zwave-sensor/src/configurations.lua +++ b/drivers/SmartThings/zwave-sensor/src/configurations.lua @@ -12,6 +12,7 @@ -- See the License for the specific language governing permissions and -- limitations under the License. +local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) --- @type st.zwave.CommandClass.Association @@ -345,6 +346,20 @@ local devices = { ASSOCIATION = { {grouping_identifier = 1} } + }, + ZOOZ_ZSE42_WATER_LEAK = { + MATCHING_MATRIX = { + mfrs = 0x027A, + product_types = 0x7000, + product_ids = 0xE002 + }, + BATTERY = { + quantity = 1, + type = "CR2450" + }, + WAKE_UP = { + { seconds = 21600 } --6 hours + } } } local configurations = {} @@ -376,6 +391,11 @@ configurations.initial_configuration = function(driver, device) device:send(WakeUp:IntervalSet({seconds = value.seconds, node_id = _node_id})) end end + local battery = configurations.get_device_battery(device) + if battery ~= nil then + device:emit_event(capabilities.battery.quantity({ value = battery.quantity or 1 })) + device:emit_event(capabilities.battery.type({ value = battery.type or "Unspecified" })) + end end configurations.get_device_configuration = function(zw_device) @@ -426,4 +446,16 @@ configurations.get_device_wake_up = function(zw_device) return nil end +configurations.get_device_battery = function(zw_device) + for _, device in pairs(devices) do + if zw_device:id_match( + device.MATCHING_MATRIX.mfrs, + device.MATCHING_MATRIX.product_types, + device.MATCHING_MATRIX.product_ids) then + return device.BATTERY + end + end + return nil +end + return configurations diff --git a/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua b/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua new file mode 100644 index 0000000000..058a7f955c --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/firmware-version/init.lua @@ -0,0 +1,91 @@ +-- Copyright 2025 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" +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +--- @type st.zwave.CommandClass.Version +local Version = (require "st.zwave.CommandClass.Version")({ version = 1 }) +--- @type st.zwave.CommandClass.WakeUp +local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) + +--This sub_driver will populate the currentVersion (firmware) when the firmwareUpdate capability is enabled +local FINGERPRINTS = { + { manufacturerId = 0x027A, productType = 0x7000, productId = 0xE002 } -- Zooz ZSE42 Water Sensor +} + +local function can_handle_fw(opts, driver, device, ...) + if device:supports_capability_by_id(capabilities.firmwareUpdate.ID) then + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subDriver = require("firmware-version") + return true, subDriver + end + end + end + return false +end + +--Runs upstream handlers (ex zwave_handlers) +local function call_parent_handler(handlers, self, device, event, args) + for _, func in ipairs(handlers or {}) do + func(self, device, event, args) + end +end + +--Request version if not populated yet +local function send_version_get(driver, device) + if device:get_latest_state("main", capabilities.firmwareUpdate.ID, capabilities.firmwareUpdate.currentVersion.NAME) == nil then + device:send(Version:Get({})) + end +end + +local function version_report(driver, device, cmd) + local major = cmd.args.application_version + local minor = cmd.args.application_sub_version + local fmtFirmwareVersion = string.format("%d.%02d", major, minor) + device:emit_event(capabilities.firmwareUpdate.currentVersion({ value = fmtFirmwareVersion })) +end + +local function wakeup_notification(driver, device, cmd) + send_version_get(driver, device) + --Call parent WakeUp functions + call_parent_handler(driver.zwave_handlers[cc.WAKE_UP][WakeUp.NOTIFICATION], driver, device, cmd) +end + +local function added_handler(driver, device) + --Call main function + driver.lifecycle_handlers.added(driver, device) + --Extras for this sub_driver + send_version_get(driver, device) +end + +local firmware_version = { + NAME = "firmware_version", + can_handle = can_handle_fw, + + lifecycle_handlers = { + added = added_handler, + }, + zwave_handlers = { + [cc.VERSION] = { + [Version.REPORT] = version_report + }, + [cc.WAKE_UP] = { + [WakeUp.NOTIFICATION] = wakeup_notification + } + } +} + +return firmware_version \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/init.lua b/drivers/SmartThings/zwave-sensor/src/init.lua index b8cc862cd8..feb03115c2 100644 --- a/drivers/SmartThings/zwave-sensor/src/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/init.lua @@ -152,6 +152,7 @@ local driver_template = { lazy_load_if_possible("v1-contact-event"), lazy_load_if_possible("timed-tamper-clear"), lazy_load_if_possible("wakeup-no-poll"), + lazy_load_if_possible("firmware-version"), lazy_load_if_possible("apiv6_bugfix"), lazy_load_if_possible("aeotec-door-window-sensor-8"), lazy_load_if_possible("aeotec-aerq-8"), diff --git a/drivers/SmartThings/zwave-sensor/src/preferences.lua b/drivers/SmartThings/zwave-sensor/src/preferences.lua index e7f67f07a3..275656dc6a 100644 --- a/drivers/SmartThings/zwave-sensor/src/preferences.lua +++ b/drivers/SmartThings/zwave-sensor/src/preferences.lua @@ -162,6 +162,19 @@ local devices = { motionNotdetRepT = {parameter_number = 160, size = 2}, }, }, + ZOOZ_ZSE42_WATER_LEAK = { + MATCHING_MATRIX = { + mfrs = 0x027A, + product_types = 0x7000, + product_ids = 0xE002 + }, + PARAMETERS = { + ledIndicator = { parameter_number = 1, size = 1 }, + leakAlertClearDelay = { parameter_number = 2, size = 4 }, + batteryThreshold = { parameter_number = 3, size = 1 }, + lowBatteryAlert = { parameter_number = 4, size = 1 }, + }, + }, AEOTEC_AERQ_8 = { MATCHING_MATRIX = { mfrs = 0x0371, diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua b/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua new file mode 100644 index 0000000000..9790b5cfb5 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/test/test_firmware_version.lua @@ -0,0 +1,91 @@ +-- Copyright 2026 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" +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +local zw_test_utils = require "integration_test.zwave_test_utils" +local t_utils = require "integration_test.utils" +--- @type st.zwave.CommandClass.Version +local Version = (require "st.zwave.CommandClass.Version")({ version = 1 }) +--- @type st.zwave.CommandClass.WakeUp +local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) +--- @type st.zwave.CommandClass.Battery +local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) + +local sensor_endpoints = { + { + command_classes = { + { value = cc.WAKE_UP }, + { value = cc.BATTERY }, + } + } +} + +local mock_device = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("zooz-zse42-water.yml"), + zwave_endpoints = sensor_endpoints, + zwave_manufacturer_id = 0x027A, + zwave_product_type = 0x7000, + zwave_product_id = 0xE002, +}) + +local function test_init() + test.mock_device.add_test_device(mock_device) +end + +test.set_test_init_function(test_init) + +test.register_message_test( + "Wakeup notification should not poll binary sensor if device has contact state", + { + { + channel = "zwave", + direction = "receive", + message = { mock_device.id, zw_test_utils.zwave_test_build_receive_command(WakeUp:Notification({ })) } + }, + --This is sent by the sub-driver + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_device, + Version:Get({}) + ) + }, + --This is sent by the main driver + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_device, + WakeUp:IntervalGetV1({}) + ) + }, + --This is sent by the main driver + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_device, + Battery:Get({ }) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua index 2007bedb0d..491960ed26 100644 --- a/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua @@ -20,22 +20,45 @@ local capabilities = require "st.capabilities" local TAMPER_TIMER = "_tamper_timer" local TAMPER_CLEAR = 10 -local FIBARO_DOOR_WINDOW_MFR_ID = 0x010F -local function can_handle_tamper_event(opts, driver, device, cmd, ...) - if device.zwave_manufacturer_id ~= FIBARO_DOOR_WINDOW_MFR_ID and - opts.dispatcher_class == "ZwaveDispatcher" and +local excluded_devices = { + FIBARO_DOOR_WINDOW = { + mfrs = 0x010F + } +} + +local function can_handle_tamper_event(opts, driver, zw_device, cmd, ...) + -- check only for relevant tamper event first + if not(opts.dispatcher_class == "ZwaveDispatcher" and cmd ~= nil and cmd.cmd_class ~= nil and cmd.cmd_class == cc.NOTIFICATION and cmd.cmd_id == Notification.REPORT and cmd.args.notification_type == Notification.notification_type.HOME_SECURITY and (cmd.args.event == Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED or - cmd.args.event == Notification.event.home_security.TAMPERING_PRODUCT_MOVED) then - local subdriver = require("timed-tamper-clear") - return true, subdriver - else return false + cmd.args.event == Notification.event.home_security.TAMPERING_PRODUCT_MOVED)) then + return false + end + + -- check exclusion list: if device matches any entry, skip auto-clear + for _, excluded_device in pairs(excluded_devices) do + local mfrs = excluded_device.mfrs + local product_types = excluded_device.product_types or nil + local product_ids = excluded_device.product_ids or nil + + if mfrs ~= nil then + if zw_device:id_match( + mfrs, + product_types, + product_ids + ) then + return false + end end + end + + local subdriver = require("timed-tamper-clear") + return true, subdriver end -- This behavior is from zwave-door-window-sensor.groovy. We've seen this behavior diff --git a/drivers/SmartThings/zwave-siren/src/aeon-siren/can_handle.lua b/drivers/SmartThings/zwave-siren/src/aeon-siren/can_handle.lua new file mode 100644 index 0000000000..2aae744ccc --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/aeon-siren/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_aeon_siren(opts, driver, device, ...) + local AEON_MFR = 0x0086 + local AEON_SIREN_PRODUCT_ID = 0x0050 + + if device.zwave_manufacturer_id == AEON_MFR and device.zwave_product_id == AEON_SIREN_PRODUCT_ID then + return true, require("aeon-siren") + end + return false +end + +return can_handle_aeon_siren diff --git a/drivers/SmartThings/zwave-siren/src/aeon-siren/init.lua b/drivers/SmartThings/zwave-siren/src/aeon-siren/init.lua index f98ba06d83..1c1a2bb0b1 100644 --- a/drivers/SmartThings/zwave-siren/src/aeon-siren/init.lua +++ b/drivers/SmartThings/zwave-siren/src/aeon-siren/init.lua @@ -1,32 +1,16 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local Basic = (require "st.zwave.CommandClass.Basic")({ version=1 }) local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=1 }) -local AEON_MFR = 0x0086 -local AEON_SIREN_PRODUCT_ID = 0x0050 - local SOUND_TYPE_AND_VOLUME_PARAMETER_NUMBER = 37 local CONFIGURE_SOUND_TYPE = "type" local SOUND_TYPE_DEFAULT = 1 local CONFIGURE_VOLUME = "volume" local VOLUME_DEFAULT = 3 -local function can_handle_aeon_siren(opts, driver, device, ...) - return device.zwave_manufacturer_id == AEON_MFR and device.zwave_product_id == AEON_SIREN_PRODUCT_ID -end local function configure_sound(device, sound_type, volume) if sound_type == nil then sound_type = SOUND_TYPE_DEFAULT end @@ -64,7 +48,7 @@ end local aeon_siren = { NAME = "aeon-siren", - can_handle = can_handle_aeon_siren, + can_handle = require("aeon-siren.can_handle"), lifecycle_handlers = { doConfigure = do_configure, infoChanged = info_changed diff --git a/drivers/SmartThings/zwave-siren/src/aeotec-doorbell-siren/can_handle.lua b/drivers/SmartThings/zwave-siren/src/aeotec-doorbell-siren/can_handle.lua new file mode 100644 index 0000000000..31304e8688 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/aeotec-doorbell-siren/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_aeotec_doorbell_siren(opts, driver, device, ...) + local FINGERPRINTS = require("aeotec-doorbell-siren.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("aeotec-doorbell-siren") + end + end + return false +end + +return can_handle_aeotec_doorbell_siren diff --git a/drivers/SmartThings/zwave-siren/src/aeotec-doorbell-siren/fingerprints.lua b/drivers/SmartThings/zwave-siren/src/aeotec-doorbell-siren/fingerprints.lua new file mode 100644 index 0000000000..782dfbc0c9 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/aeotec-doorbell-siren/fingerprints.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEOTEC_DOORBELL_SIREN_FINGERPRINTS = { + { manufacturerId = 0x0371, productType = 0x0003, productId = 0x00A2}, -- Aeotec Doorbell 6 (EU) + { manufacturerId = 0x0371, productType = 0x0103, productId = 0x00A2}, -- Aeotec Doorbell 6 (US) + { manufacturerId = 0x0371, productType = 0x0203, productId = 0x00A2}, -- Aeotec Doorbell 6 (AU) + { manufacturerId = 0x0371, productType = 0x0003, productId = 0x00A4}, -- Aeotec Siren 6 (EU) + { manufacturerId = 0x0371, productType = 0x0103, productId = 0x00A4}, -- Aeotec Siren 6 (US) + { manufacturerId = 0x0371, productType = 0x0203, productId = 0x00A4}, -- Aeotec Siren 6 (AU) +} + +return AEOTEC_DOORBELL_SIREN_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-siren/src/aeotec-doorbell-siren/init.lua b/drivers/SmartThings/zwave-siren/src/aeotec-doorbell-siren/init.lua index bab6909235..35f006e94f 100644 --- a/drivers/SmartThings/zwave-siren/src/aeotec-doorbell-siren/init.lua +++ b/drivers/SmartThings/zwave-siren/src/aeotec-doorbell-siren/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local cc = require "st.zwave.CommandClass" @@ -20,14 +10,6 @@ local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) local SoundSwitch = (require "st.zwave.CommandClass.SoundSwitch")({version=1}) local preferencesMap = require "preferences" -local AEOTEC_DOORBELL_SIREN_FINGERPRINTS = { - { manufacturerId = 0x0371, productType = 0x0003, productId = 0x00A2}, -- Aeotec Doorbell 6 (EU) - { manufacturerId = 0x0371, productType = 0x0103, productId = 0x00A2}, -- Aeotec Doorbell 6 (US) - { manufacturerId = 0x0371, productType = 0x0203, productId = 0x00A2}, -- Aeotec Doorbell 6 (AU) - { manufacturerId = 0x0371, productType = 0x0003, productId = 0x00A4}, -- Aeotec Siren 6 (EU) - { manufacturerId = 0x0371, productType = 0x0103, productId = 0x00A4}, -- Aeotec Siren 6 (US) - { manufacturerId = 0x0371, productType = 0x0203, productId = 0x00A4}, -- Aeotec Siren 6 (AU) -} local COMPONENT_NAME = "componentName" local TONE = "tone" @@ -51,14 +33,6 @@ local BUTTON_BATTERY_NORMAL = 99 local DEVICE_PROFILE_CHANGE_IN_PROGRESS = "device_profile_change_in_progress" local NEXT_BUTTON_BATTERY_EVENT_DETAILS = "next_button_battery_event_details" -local function can_handle_aeotec_doorbell_siren(opts, driver, device, ...) - for _, fingerprint in ipairs(AEOTEC_DOORBELL_SIREN_FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - return false -end local function querySoundStatus(device) for endpoint = 2, NUMBER_OF_SOUND_COMPONENTS do @@ -316,7 +290,7 @@ end local aeotec_doorbell_siren = { NAME = "aeotec-doorbell-siren", - can_handle = can_handle_aeotec_doorbell_siren, + can_handle = require("aeotec-doorbell-siren.can_handle"), lifecycle_handlers = { added = device_added, diff --git a/drivers/SmartThings/zwave-siren/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-siren/src/apiv6_bugfix/can_handle.lua new file mode 100644 index 0000000000..3f4b44c1e0 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/apiv6_bugfix/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle(opts, driver, device, cmd, ...) + local cc = require "st.zwave.CommandClass" + local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) + local version = require "version" + if version.api == 6 and + cmd.cmd_class == cc.WAKE_UP and + cmd.cmd_id == WakeUp.NOTIFICATION + then + return true, require("apiv6_bugfix") + end + return false +end + +return can_handle diff --git a/drivers/SmartThings/zwave-siren/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-siren/src/apiv6_bugfix/init.lua index 0204b7b2d5..2e7e3ca3b8 100644 --- a/drivers/SmartThings/zwave-siren/src/apiv6_bugfix/init.lua +++ b/drivers/SmartThings/zwave-siren/src/apiv6_bugfix/init.lua @@ -1,13 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cc = require "st.zwave.CommandClass" local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) -local function can_handle(opts, driver, device, cmd, ...) - local version = require "version" - return version.api == 6 and - cmd.cmd_class == cc.WAKE_UP and - cmd.cmd_id == WakeUp.NOTIFICATION -end local function wakeup_notification(driver, device, cmd) device:refresh() @@ -20,7 +17,7 @@ local apiv6_bugfix = { } }, NAME = "apiv6_bugfix", - can_handle = can_handle + can_handle = require("apiv6_bugfix.can_handle"), } return apiv6_bugfix diff --git a/drivers/SmartThings/zwave-siren/src/configurations.lua b/drivers/SmartThings/zwave-siren/src/configurations.lua index b17b3cbc0f..5165ee4551 100644 --- a/drivers/SmartThings/zwave-siren/src/configurations.lua +++ b/drivers/SmartThings/zwave-siren/src/configurations.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local devices = { YALE_SIREN = { diff --git a/drivers/SmartThings/zwave-siren/src/ecolink-wireless-siren/can_handle.lua b/drivers/SmartThings/zwave-siren/src/ecolink-wireless-siren/can_handle.lua new file mode 100644 index 0000000000..5803087913 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/ecolink-wireless-siren/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_ecolink_wireless_siren(opts, driver, device, ...) + local FINGERPRINTS = require("ecolink-wireless-siren.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("ecolink-wireless-siren") + end + end + return false +end + +return can_handle_ecolink_wireless_siren diff --git a/drivers/SmartThings/zwave-siren/src/ecolink-wireless-siren/fingerprints.lua b/drivers/SmartThings/zwave-siren/src/ecolink-wireless-siren/fingerprints.lua new file mode 100644 index 0000000000..9edaef0374 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/ecolink-wireless-siren/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ECOLINK_WIRELESS_SIREN_FINGERPRINTS = { + { manufacturerId = 0x014A, productType = 0x0005, productId = 0x000A }, -- Ecolink Siren +} + +return ECOLINK_WIRELESS_SIREN_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-siren/src/ecolink-wireless-siren/init.lua b/drivers/SmartThings/zwave-siren/src/ecolink-wireless-siren/init.lua index 0413221185..6b5a2a25ad 100644 --- a/drivers/SmartThings/zwave-siren/src/ecolink-wireless-siren/init.lua +++ b/drivers/SmartThings/zwave-siren/src/ecolink-wireless-siren/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -20,18 +10,7 @@ local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) --- @type st.zwave.CommandClass.SwitchBinary local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({ version = 2 }) -local ECOLINK_WIRELESS_SIREN_FINGERPRINTS = { - { manufacturerId = 0x014A, productType = 0x0005, productId = 0x000A }, -- Ecolink Siren -} -local function can_handle_ecolink_wireless_siren(opts, driver, device, ...) - for _, fingerprint in ipairs(ECOLINK_WIRELESS_SIREN_FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - return false -end local function basic_set_handler(driver, device, cmd) local value = cmd.args.target_value and cmd.args.target_value or cmd.args.value @@ -103,7 +82,7 @@ local ecolink_wireless_siren = { lifecycle_handlers = { init = device_init }, - can_handle = can_handle_ecolink_wireless_siren, + can_handle = require("ecolink-wireless-siren.can_handle"), } return ecolink_wireless_siren diff --git a/drivers/SmartThings/zwave-siren/src/fortrezz/can_handle.lua b/drivers/SmartThings/zwave-siren/src/fortrezz/can_handle.lua new file mode 100644 index 0000000000..83289ab86a --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/fortrezz/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fortrezz_siren(opts, self, device, ...) + if device.zwave_manufacturer_id == 0x0084 and + device.zwave_product_type == 0x0313 and + device.zwave_product_id == 0x010B then + return true, require("fortrezz") + end + return false +end + +return can_handle_fortrezz_siren diff --git a/drivers/SmartThings/zwave-siren/src/fortrezz/init.lua b/drivers/SmartThings/zwave-siren/src/fortrezz/init.lua index 73a3e6e0d4..8bf31178f6 100644 --- a/drivers/SmartThings/zwave-siren/src/fortrezz/init.lua +++ b/drivers/SmartThings/zwave-siren/src/fortrezz/init.lua @@ -1,26 +1,11 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cc = require "st.zwave.CommandClass" local capabilities = require "st.capabilities" local Basic = (require "st.zwave.CommandClass.Basic")({version=1}) -local function can_handle_fortrezz_siren(opts, self, device, ...) - return device.zwave_manufacturer_id == 0x0084 and - device.zwave_product_type == 0x0313 and - device.zwave_product_id == 0x010B -end local function set_and_get(value) return function (self, device, command) @@ -47,7 +32,7 @@ end local fortrezz_siren = { NAME = "fortrezz-siren", - can_handle = can_handle_fortrezz_siren, + can_handle = require("fortrezz.can_handle"), capability_handlers = { [capabilities.alarm.ID] = { [capabilities.alarm.commands.siren.NAME] = set_and_get(0x42), @@ -67,4 +52,4 @@ local fortrezz_siren = { } } -return fortrezz_siren \ No newline at end of file +return fortrezz_siren diff --git a/drivers/SmartThings/zwave-siren/src/init.lua b/drivers/SmartThings/zwave-siren/src/init.lua index a725e8b79c..b682ea77b7 100644 --- a/drivers/SmartThings/zwave-siren/src/init.lua +++ b/drivers/SmartThings/zwave-siren/src/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local cap_defaults = require "st.capabilities.defaults" @@ -92,19 +82,7 @@ local driver_template = { capabilities.relativeHumidityMeasurement, capabilities.chime }, - sub_drivers = { - require("multifunctional-siren"), - require("zwave-sound-sensor"), - require("ecolink-wireless-siren"), - require("philio-sound-siren"), - require("aeotec-doorbell-siren"), - require("aeon-siren"), - require("yale-siren"), - require("zipato-siren"), - require("utilitech-siren"), - require("fortrezz"), - require("apiv6_bugfix"), - }, + sub_drivers = require("sub_drivers"), lifecycle_handlers = { infoChanged = info_changed, doConfigure = do_configure, diff --git a/drivers/SmartThings/zwave-siren/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-siren/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..45115081e4 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/lazy_load_subdriver.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +return function(sub_driver_name) + -- gets the current lua libs api version + local ZwaveDriver = require "st.zwave.driver" + local version = require "version" + + if version.api >= 16 then + return ZwaveDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZwaveDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end + +end diff --git a/drivers/SmartThings/zwave-siren/src/multifunctional-siren/can_handle.lua b/drivers/SmartThings/zwave-siren/src/multifunctional-siren/can_handle.lua new file mode 100644 index 0000000000..a3c62d02aa --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/multifunctional-siren/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_multifunctional_siren(opts, driver, device, ...) + local FINGERPRINTS = require("multifunctional-siren.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("multifunctional-siren") + end + end + return false +end + +return can_handle_multifunctional_siren diff --git a/drivers/SmartThings/zwave-siren/src/multifunctional-siren/fingerprints.lua b/drivers/SmartThings/zwave-siren/src/multifunctional-siren/fingerprints.lua new file mode 100644 index 0000000000..d2bcf5402d --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/multifunctional-siren/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local MULTIFUNCTIONAL_SIREN_FINGERPRINTS = { + { manufacturerId = 0x027A, productType = 0x000C, productId = 0x0003 }, -- Zooz S2 Multisiren ZSE19 + { manufacturerId = 0x0060, productType = 0x000C, productId = 0x0003 } -- Everspring Indoor Voice Siren +} + +return MULTIFUNCTIONAL_SIREN_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-siren/src/multifunctional-siren/init.lua b/drivers/SmartThings/zwave-siren/src/multifunctional-siren/init.lua index 2c8af3bb26..b1bca996d5 100644 --- a/drivers/SmartThings/zwave-siren/src/multifunctional-siren/init.lua +++ b/drivers/SmartThings/zwave-siren/src/multifunctional-siren/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -20,24 +10,12 @@ local Basic = (require "st.zwave.CommandClass.Basic")({version=1}) --- @type st.zwave.CommandClass.Battery local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) -local MULTIFUNCTIONAL_SIREN_FINGERPRINTS = { - { manufacturerId = 0x027A, productType = 0x000C, productId = 0x0003 }, -- Zooz S2 Multisiren ZSE19 - { manufacturerId = 0x0060, productType = 0x000C, productId = 0x0003 } -- Everspring Indoor Voice Siren -} --- Determine whether the passed device is multifunctional siren --- --- @param driver Driver driver instance --- @param device Device device isntance --- @return boolean true if the device proper, else false -local function can_handle_multifunctional_siren(opts, driver, device, ...) - for _, fingerprint in ipairs(MULTIFUNCTIONAL_SIREN_FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - return false -end --- Default handler for notification command class reports --- @@ -74,7 +52,7 @@ local multifunctional_siren = { doConfigure = do_configure }, NAME = "multifunctional siren", - can_handle = can_handle_multifunctional_siren, + can_handle = require("multifunctional-siren.can_handle"), } return multifunctional_siren diff --git a/drivers/SmartThings/zwave-siren/src/philio-sound-siren/can_handle.lua b/drivers/SmartThings/zwave-siren/src/philio-sound-siren/can_handle.lua new file mode 100644 index 0000000000..23e614a2e2 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/philio-sound-siren/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_philio_sound_siren(opts, driver, device, ...) + local FINGERPRINTS = require("philio-sound-siren.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("philio-sound-siren") + end + end + return false +end + +return can_handle_philio_sound_siren diff --git a/drivers/SmartThings/zwave-siren/src/philio-sound-siren/fingerprints.lua b/drivers/SmartThings/zwave-siren/src/philio-sound-siren/fingerprints.lua new file mode 100644 index 0000000000..e1f34617fc --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/philio-sound-siren/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local PHILIO_SOUND_SIREN = { + { manufacturerId = 0x013C, productType = 0x0004, productId = 0x000A } +} + +return PHILIO_SOUND_SIREN diff --git a/drivers/SmartThings/zwave-siren/src/philio-sound-siren/init.lua b/drivers/SmartThings/zwave-siren/src/philio-sound-siren/init.lua index 6397022dd8..23e310bf14 100644 --- a/drivers/SmartThings/zwave-siren/src/philio-sound-siren/init.lua +++ b/drivers/SmartThings/zwave-siren/src/philio-sound-siren/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cc = require "st.zwave.CommandClass" local capabilities = require "st.capabilities" @@ -19,9 +9,6 @@ local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({version=2}) local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) local preferencesMap = require "preferences" -local PHILIO_SOUND_SIREN = { - { manufacturerId = 0x013C, productType = 0x0004, productId = 0x000A } -} local PARAMETER_SOUND = "sound" local SMOKE = 0 @@ -43,14 +30,6 @@ local sounds = { [SMOKE] = {notificationType = Notification.notification_type.SMOKE, event = Notification.event.smoke.DETECTED_LOCATION_PROVIDED} } -local function can_handle_philio_sound_siren(opts, driver, device, ...) - for _, fingerprint in ipairs(PHILIO_SOUND_SIREN) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - return false -end local function device_added(self, device) device:refresh() @@ -157,7 +136,7 @@ end local philio_sound_siren = { NAME = "Philio sound siren", - can_handle = can_handle_philio_sound_siren, + can_handle = require("philio-sound-siren.can_handle"), lifecycle_handlers = { added = device_added }, diff --git a/drivers/SmartThings/zwave-siren/src/preferences.lua b/drivers/SmartThings/zwave-siren/src/preferences.lua index 3e9f8333ab..2c10de6fcb 100644 --- a/drivers/SmartThings/zwave-siren/src/preferences.lua +++ b/drivers/SmartThings/zwave-siren/src/preferences.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local devices = { PHILIO_SOUND_SIREN = { diff --git a/drivers/SmartThings/zwave-siren/src/sub_drivers.lua b/drivers/SmartThings/zwave-siren/src/sub_drivers.lua new file mode 100644 index 0000000000..a20d559e44 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/sub_drivers.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" +local sub_drivers = { + lazy_load_if_possible("multifunctional-siren"), + lazy_load_if_possible("zwave-sound-sensor"), + lazy_load_if_possible("ecolink-wireless-siren"), + lazy_load_if_possible("philio-sound-siren"), + lazy_load_if_possible("aeotec-doorbell-siren"), + lazy_load_if_possible("aeon-siren"), + lazy_load_if_possible("yale-siren"), + lazy_load_if_possible("zipato-siren"), + lazy_load_if_possible("utilitech-siren"), + lazy_load_if_possible("fortrezz"), + lazy_load_if_possible("apiv6_bugfix"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-siren/src/test/test_aeon_siren.lua b/drivers/SmartThings/zwave-siren/src/test/test_aeon_siren.lua index 714dc17a10..34cdc4ceea 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_aeon_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_aeon_siren.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-siren/src/test/test_aeotec_doorbell_siren.lua b/drivers/SmartThings/zwave-siren/src/test/test_aeotec_doorbell_siren.lua index c2dc708cfe..924ef4e4f2 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_aeotec_doorbell_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_aeotec_doorbell_siren.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-siren/src/test/test_ecolink_wireless_siren.lua b/drivers/SmartThings/zwave-siren/src/test/test_ecolink_wireless_siren.lua index f20e5c6074..54b4243331 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_ecolink_wireless_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_ecolink_wireless_siren.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-siren/src/test/test_fortrezz_siren.lua b/drivers/SmartThings/zwave-siren/src/test/test_fortrezz_siren.lua index e23b6d3caf..e99cfce77c 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_fortrezz_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_fortrezz_siren.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local zw = require "st.zwave" @@ -163,4 +153,4 @@ test.register_coroutine_test( end ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-siren/src/test/test_philio_sound_siren.lua b/drivers/SmartThings/zwave-siren/src/test/test_philio_sound_siren.lua index 23ad20a1fd..d825be6cec 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_philio_sound_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_philio_sound_siren.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-siren/src/test/test_utilitech_siren.lua b/drivers/SmartThings/zwave-siren/src/test/test_utilitech_siren.lua index 4c24c069ad..e62141ebcf 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_utilitech_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_utilitech_siren.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-siren/src/test/test_yale_siren.lua b/drivers/SmartThings/zwave-siren/src/test/test_yale_siren.lua index 072a55c3d3..a7b7123f38 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_yale_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_yale_siren.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-siren/src/test/test_zipato_siren.lua b/drivers/SmartThings/zwave-siren/src/test/test_zipato_siren.lua index fbfbdd2db7..6b912be8bb 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_zipato_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_zipato_siren.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-siren/src/test/test_zwave_multifunctional-siren.lua b/drivers/SmartThings/zwave-siren/src/test/test_zwave_multifunctional-siren.lua index c0d58bb952..a40b510b49 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_zwave_multifunctional-siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_zwave_multifunctional-siren.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-siren/src/test/test_zwave_notification_siren.lua b/drivers/SmartThings/zwave-siren/src/test/test_zwave_notification_siren.lua index 73de507946..0e0f407523 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_zwave_notification_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_zwave_notification_siren.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-siren/src/test/test_zwave_siren.lua b/drivers/SmartThings/zwave-siren/src/test/test_zwave_siren.lua index 75713b89b7..121f6170e2 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_zwave_siren.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_zwave_siren.lua @@ -1,4 +1,4 @@ ----@diagnostic disable: param-type-mismatch, undefined-field +-- Copyright 2022 SmartThings, Inc. -- Copyright 2022 SmartThings -- -- Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/drivers/SmartThings/zwave-siren/src/test/test_zwave_sound_sensor.lua b/drivers/SmartThings/zwave-siren/src/test/test_zwave_sound_sensor.lua index e88b1e1852..10595c7137 100644 --- a/drivers/SmartThings/zwave-siren/src/test/test_zwave_sound_sensor.lua +++ b/drivers/SmartThings/zwave-siren/src/test/test_zwave_sound_sensor.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-siren/src/utilitech-siren/can_handle.lua b/drivers/SmartThings/zwave-siren/src/utilitech-siren/can_handle.lua new file mode 100644 index 0000000000..72f6a963b9 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/utilitech-siren/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_utilitech_siren(opts, driver, device, ...) + local UTILITECH_MFR = 0x0060 + local UTILITECH_SIREN_PRODUCT_ID = 0x0001 + if device.zwave_manufacturer_id == UTILITECH_MFR and device.zwave_product_id == UTILITECH_SIREN_PRODUCT_ID then + return true, require("utilitech-siren") + end + return false +end + +return can_handle_utilitech_siren diff --git a/drivers/SmartThings/zwave-siren/src/utilitech-siren/init.lua b/drivers/SmartThings/zwave-siren/src/utilitech-siren/init.lua index 9e6818c6e4..0088fa6dd8 100644 --- a/drivers/SmartThings/zwave-siren/src/utilitech-siren/init.lua +++ b/drivers/SmartThings/zwave-siren/src/utilitech-siren/init.lua @@ -1,29 +1,12 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cc = require "st.zwave.CommandClass" local Basic = (require "st.zwave.CommandClass.Basic")({version=1,strict=true}) local Battery = (require "st.zwave.CommandClass.Battery")({version=1}) local BatteryDefaults = require "st.zwave.defaults.battery" -local UTILITECH_MFR = 0x0060 -local UTILITECH_SIREN_PRODUCT_ID = 0x0001 - -local function can_handle_utilitech_siren(opts, driver, device, ...) - return device.zwave_manufacturer_id == UTILITECH_MFR and device.zwave_product_id == UTILITECH_SIREN_PRODUCT_ID -end - local function device_added(self, device) device:send(Basic:Get({})) device:send(Battery:Get({})) @@ -39,7 +22,7 @@ end local utilitech_siren = { NAME = "utilitech-siren", - can_handle = can_handle_utilitech_siren, + can_handle = require("utilitech-siren.can_handle"), zwave_handlers = { [cc.BATTERY] = { [Battery.REPORT] = battery_report_handler diff --git a/drivers/SmartThings/zwave-siren/src/yale-siren/can_handle.lua b/drivers/SmartThings/zwave-siren/src/yale-siren/can_handle.lua new file mode 100644 index 0000000000..1e29867936 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/yale-siren/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_yale_siren(opts, self, device, ...) + local YALE_MFR = 0x0129 + if device.zwave_manufacturer_id == YALE_MFR then + return true, require("yale-siren") + end + return false +end + +return can_handle_yale_siren diff --git a/drivers/SmartThings/zwave-siren/src/yale-siren/init.lua b/drivers/SmartThings/zwave-siren/src/yale-siren/init.lua index de548aee95..3757a2c3a9 100644 --- a/drivers/SmartThings/zwave-siren/src/yale-siren/init.lua +++ b/drivers/SmartThings/zwave-siren/src/yale-siren/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cc = require "st.zwave.CommandClass" local capabilities = require "st.capabilities" @@ -20,12 +10,6 @@ local Configuration = (require "st.zwave.CommandClass.Configuration")({version=1 local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({version=1}) local preferencesMap = require "preferences" -local YALE_MFR = 0x0129 - -local function can_handle_yale_siren(opts, self, device, ...) - return device.zwave_manufacturer_id == YALE_MFR -end - local function siren_set_helper(device, value) device:send(Basic:Set({value = value})) local query_device = function() @@ -98,7 +82,7 @@ end local yale_siren = { NAME = "yale-siren", - can_handle = can_handle_yale_siren, + can_handle = require("yale-siren.can_handle"), capability_handlers = { [capabilities.alarm.ID] = { [capabilities.alarm.commands.both.NAME] = siren_on, diff --git a/drivers/SmartThings/zwave-siren/src/zipato-siren/can_handle.lua b/drivers/SmartThings/zwave-siren/src/zipato-siren/can_handle.lua new file mode 100644 index 0000000000..cb1170db2e --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/zipato-siren/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_zipato_siren(opts, driver, device, ...) + local ZIPATO_MFR = 0x0131 + if device.zwave_manufacturer_id == ZIPATO_MFR then + return true, require("zipato-siren") + end + return false +end + +return can_handle_zipato_siren diff --git a/drivers/SmartThings/zwave-siren/src/zipato-siren/init.lua b/drivers/SmartThings/zwave-siren/src/zipato-siren/init.lua index e22e643ae1..2af57daa19 100644 --- a/drivers/SmartThings/zwave-siren/src/zipato-siren/init.lua +++ b/drivers/SmartThings/zwave-siren/src/zipato-siren/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local cc = require "st.zwave.CommandClass" @@ -18,13 +8,9 @@ local AlarmDefaults = require "st.zwave.defaults.alarm" local Basic = (require "st.zwave.CommandClass.Basic")({version=1}) local Battery = (require "st.zwave.CommandClass.Battery")({version=1}) -local ZIPATO_MFR = 0x0131 local BASIC_AND_SWITCH_BINARY_REPORT_STROBE_LIMIT = 33 local BASIC_AND_SWITCH_BINARY_REPORT_SIREN_LIMIT = 66 -local function can_handle_zipato_siren(opts, driver, device, ...) - return device.zwave_manufacturer_id == ZIPATO_MFR -end local function basic_report_handler(driver, device, cmd) local value = cmd.args.value @@ -64,7 +50,7 @@ end local zipato_siren = { NAME = "zipato-siren", - can_handle = can_handle_zipato_siren, + can_handle = require("zipato-siren.can_handle"), capability_handlers = { [capabilities.alarm.ID] = { [capabilities.alarm.commands.both.NAME] = siren_on, diff --git a/drivers/SmartThings/zwave-siren/src/zwave-sound-sensor/can_handle.lua b/drivers/SmartThings/zwave-siren/src/zwave-sound-sensor/can_handle.lua new file mode 100644 index 0000000000..c3fb8b343e --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/zwave-sound-sensor/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_zwave_sound_sensor(opts, driver, device, ...) + local FINGERPRINTS = require("zwave-sound-sensor.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("zwave-sound-sensor") + end + end + return false +end + +return can_handle_zwave_sound_sensor diff --git a/drivers/SmartThings/zwave-siren/src/zwave-sound-sensor/fingerprints.lua b/drivers/SmartThings/zwave-siren/src/zwave-sound-sensor/fingerprints.lua new file mode 100644 index 0000000000..e0c8ef4762 --- /dev/null +++ b/drivers/SmartThings/zwave-siren/src/zwave-sound-sensor/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local ZWAVE_SOUND_SENSOR_FINGERPRINTS = { + { manufacturerId = 0x014A, productType = 0x0005, productId = 0x000F } --Ecolink Firefighter +} + +return ZWAVE_SOUND_SENSOR_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-siren/src/zwave-sound-sensor/init.lua b/drivers/SmartThings/zwave-siren/src/zwave-sound-sensor/init.lua index c591f8a89b..c50153ec26 100644 --- a/drivers/SmartThings/zwave-siren/src/zwave-sound-sensor/init.lua +++ b/drivers/SmartThings/zwave-siren/src/zwave-sound-sensor/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -18,23 +8,12 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Alarm local Alarm = (require "st.zwave.CommandClass.Alarm")({ version = 2 }) -local ZWAVE_SOUND_SENSOR_FINGERPRINTS = { - { manufacturerId = 0x014A, productType = 0x0005, productId = 0x000F } --Ecolink Firefighter -} --- Determine whether the passed device is zwave-sound-sensor --- --- @param driver Driver driver instance --- @param device Device device isntance --- @return boolean true if the device proper, else false -local function can_handle_zwave_sound_sensor(opts, driver, device, ...) - for _, fingerprint in ipairs(ZWAVE_SOUND_SENSOR_FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - return false -end --- Default handler for alarm command class reports --- @@ -73,7 +52,7 @@ local zwave_sound_sensor = { added = added_handler, }, NAME = "zwave sound sensor", - can_handle = can_handle_zwave_sound_sensor, + can_handle = require("zwave-sound-sensor.can_handle"), } return zwave_sound_sensor diff --git a/drivers/SmartThings/zwave-switch/fingerprints.yml b/drivers/SmartThings/zwave-switch/fingerprints.yml index 7154dc11c7..d90a0c6b76 100644 --- a/drivers/SmartThings/zwave-switch/fingerprints.yml +++ b/drivers/SmartThings/zwave-switch/fingerprints.yml @@ -45,17 +45,23 @@ zwaveManufacturer: productId: 0x0000 deviceProfileName: switch-level - id: "Inovelli/Dimmer/Power/Energy" - deviceLabel: Inovelli Dimmer Switch + deviceLabel: Inovelli Dimmer Red Series manufacturerId: 0x031E productType: 0x0001 productId: 0x0001 deviceProfileName: inovelli-dimmer-power-energy - id: "Inovelli/Dimmer" - deviceLabel: Inovelli Dimmer Switch + deviceLabel: Inovelli Dimmer Black Series manufacturerId: 0x031E productType: 0x0003 productId: 0x0001 deviceProfileName: inovelli-dimmer + - id: "Inovelli/VZW32-SN" + deviceLabel: Inovelli mmWave Dimmer Red Series + manufacturerId: 0x031E + productType: 0x0017 + productId: 0x0001 + deviceProfileName: inovelli-mmwave-dimmer-vzw32-sn - id: "010F/0403" deviceLabel: Fibaro Single Switch manufacturerId: 0x010F diff --git a/drivers/SmartThings/zwave-switch/profiles/inovelli-dimmer-power-energy.yml b/drivers/SmartThings/zwave-switch/profiles/inovelli-dimmer-power-energy.yml index c3ee571c10..077c80e3a3 100644 --- a/drivers/SmartThings/zwave-switch/profiles/inovelli-dimmer-power-energy.yml +++ b/drivers/SmartThings/zwave-switch/profiles/inovelli-dimmer-power-energy.yml @@ -27,24 +27,48 @@ components: categories: - name: Light - id: button1 + label: Down Button capabilities: - id: button version: 1 categories: - name: RemoteController - id: button2 + label: Up Button capabilities: - id: button version: 1 categories: - name: RemoteController - id: button3 + label: Config Button capabilities: - id: button version: 1 categories: - name: RemoteController preferences: + - name: "notificationChild" + title: "Add Child Device - Notification" + description: "Create Separate Child Device for Notification Control" + required: false + preferenceType: boolean + definition: + default: false + - name: "notificationType" + title: "Notification Effect" + description: "This is the notification effect used by the notification child device" + required: false + preferenceType: enumeration + definition: + options: + "0": "Clear" + "1": "Solid" + "2": "Chase" + "3": "Fast Blink" + "4": "Slow Blink" + "5": "Pulse" + default: 1 - name: "dimmingSpeed" title: "Dimming Speed" description: "How fast or slow the light changes state when you hold the switch. diff --git a/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml b/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml new file mode 100644 index 0000000000..d85a661839 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml @@ -0,0 +1,400 @@ +name: inovelli-mmwave-dimmer-vzw32-sn +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - id: motionSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + config: + values: + - key: "illuminance.value" + range: [0, 5000] + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + categories: + - name: Switch +- id: button1 + label: Down Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController +- id: button2 + label: Up Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController +- id: button3 + label: Config Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController +preferences: + - name: "notificationChild" + title: "Add Child Device - Notification" + description: "Create Separate Child Device for Notification Control" + required: false + preferenceType: boolean + definition: + default: false + - name: "notificationType" + title: "Notification Effect" + description: "This is the notification effect used by the notification child device" + required: false + preferenceType: enumeration + definition: + options: + "255": "Clear" + "1": "Solid" + "2": "Fast Blink" + "3": "Slow Blink" + "4": "Pulse" + "5": "Chase" + "6": "Open/Close" + "7": "Small-to-Big" + "8": "Aurora" + "9": "Slow Falling" + "10": "Medium Falling" + "11": "Fast Falling" + "12": "Slow Rising" + "13": "Medium Rising" + "14": "Fast Rising" + "15": "Medium Blink" + "16": "Slow Chase" + "17": "Fast Chase" + "18": "Fast Siren" + "19": "Slow Siren" + default: 1 + - name: "parameter158" + title: "158. Switch Mode" + description: "Use as a Dimmer or an On/Off switch" + required: true + preferenceType: enumeration + definition: + options: + "0": "Dimmer (default)" + "1": "On/Off" + default: 0 + - name: "parameter52" + title: "52. Smart Bulb Mode" + description: "For use with Smart Bulbs that need constant power and are controlled via commands rather than power. Smart Bulb Mode does not work in Dumb 3-Way Switch mode." + required: true + preferenceType: enumeration + definition: + options: + "0": "Disabled (default)" + "1": "Smart Bulb Mode" + default: 0 + - name: "parameter1" + title: "1. Dimming Speed (Remote)" + description: "This changes the speed that the light dims up when controlled from the hub. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + Default=25 (2500ms or 2.5s)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 255 + default: 25 + - name: "parameter2" + title: "2. Dimming Speed (Local)" + description: "This changes the speed that the light dims up when controlled at the switch. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=255 (Sync with parameter 1)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 255 + default: 255 + - name: "parameter3" + title: "3. Ramp Rate (Remote)" + description: "This changes the speed that the light turns on when controlled from the hub. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=255 (Sync with parameter 1)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 255 + default: 255 + - name: "parameter4" + title: "4. Ramp Rate (Local)" + description: "This changes the speed that the light turns on when controlled at the switch. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=255 (Sync with parameter 3)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 255 + default: 255 + - name: "parameter9" + title: "9. Minimum Level" + description: "The minimum level that the light can be dimmed. Useful when the user has a light that does not turn on or flickers at a lower level." + required: true + preferenceType: number + definition: + minimum: 1 + maximum: 99 + default: 1 + - name: "parameter10" + title: "10. Maximum Level" + description: "The maximum level that the light can be dimmed. Useful when the user wants to limit the maximum brighness." + required: true + preferenceType: number + definition: + minimum: 2 + maximum: 100 + default: 100 + - name: "parameter15" + title: "15. Level After Power Restored" + description: "The level the switch will return to when power is restored after power failure. + 0=Off + 1-100=Set Level + 101=Use previous level." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 101 + default: 101 + - name: "parameter18" + title: "18. Active Power Reports" + description: "Power level change that will result in a new power report being sent. + 0 = Disabled + 1-32767 = 0.1W-3276.7W." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 32767 + default: 100 + - name: "parameter19" + title: "19. Periodic Power & Energy Reports" + description: "Time period between consecutive power & energy reports being sent (in seconds). The timer is reset after each report is sent." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 32767 + default: 3600 + - name: "parameter20" + title: "20. Active Energy Reports" + description: "Energy level change that will result in a new energy report being sent. + 0 = Disabled + 1-32767 = 0.01kWh-327.67kWh." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 32767 + default: 100 + - name: "parameter50" + title: "50. Button Press Delay" + description: "Adjust the delay used in scene control. 0=no delay (disables multi-tap scenes), 1=100ms, 2=200ms, 3=300ms, etc." + required: true + preferenceType: enumeration + definition: + options: + "0": "0ms" + "1": "100ms" + "2": "200ms" + "3": "300ms" + "4": "400ms" + "5": "500ms (default)" + "6": "600ms" + "7": "700ms" + "8": "800ms" + "9": "900ms" + default: 5 + - name: "parameter95" + title: "95. LED Indicator Color (w/On)" + description: "Set the color of the Full LED Indicator when the load is on." + required: true + preferenceType: enumeration + definition: + options: + "0": "Red" + "7": "Orange" + "28": "Lemon" + "64": "Lime" + "85": "Green" + "106": "Teal" + "127": "Cyan" + "148": "Aqua" + "170": "Blue (default)" + "190": "Violet" + "212": "Magenta" + "234": "Pink" + "255": "White" + default: 170 + - name: "parameter96" + title: "96. LED Indicator Color (w/Off)" + description: "Set the color of the Full LED Indicator when the load is off." + required: true + preferenceType: enumeration + definition: + options: + "0": "Red" + "7": "Orange" + "28": "Lemon" + "64": "Lime" + "85": "Green" + "106": "Teal" + "127": "Cyan" + "148": "Aqua" + "170": "Blue (default)" + "190": "Violet" + "212": "Magenta" + "234": "Pink" + "255": "White" + default: 170 + - name: "parameter97" + title: "97. LED Indicator Intensity (w/On)" + description: "Set the intensity of the Full LED Indicator when the load is on." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 100 + default: 50 + - name: "parameter98" + title: "98. LED Indicator Intensity (w/Off)" + description: "Set the intensity of the Full LED Indicator when the load is off." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 100 + default: 5 + - name: "parameter101" + title: "101. mmWave Height Minimum (Floor)" + description: "Minimum range of the Z-Axis in cm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: -300 + - name: "parameter102" + title: "102. mmWave Height Maximum (Ceiling)" + description: "Maximum range of the Z-Axis in cm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: 300 + - name: "parameter103" + title: "103. mmWave Width Minimum (Left)" + description: "Minimum range of the X-Axis in cm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: -600 + - name: "parameter104" + title: "104. mmWave Width Maximum (Right)" + description: "Maximum range of the X-Axis in cm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: 600 + - name: "parameter105" + title: "105. mmWave Depth Minimum (Near)" + description: "Minimum range of the Y-Axis in cm" + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 600 + default: 0 + - name: "parameter106" + title: "106. mmWave Depth Maximum (Far)" + description: "Maximum range of the Y-Axis in cm" + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 600 + default: 600 + - name: "parameter108" + title: "108. mmWave Stay Life" + description: "Optimize detection in areas where user may be still for a long time. The delay time of the stay area is set to 50ms when it is set to 1, to 1 second when it is set to 20, and the default value is 300, that is, 15 seconds" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 4294967295 + default: 300 + - name: "parameter110" + title: "Light On Presence Behavior" + description: "When presence is detected, choose how to control the light load" + required: true + preferenceType: enumeration + definition: + options: + "0": "Disabled" + "1": "Auto On/Off when occupied (default)" + "2": "Auto Off when vacant" + "3": "Auto On when occupied" + "4": "Auto On/Off when Vacant" + "5": "Auto On when Vacant" + "6": "Auto Off when Occupied" + default: 1 + - name: "parameter111" + title: "111. mmWave Control Commands" + description: "Advanced commands to send to the mmWave Module (Please see documentation)" + required: false + preferenceType: enumeration + definition: + options: + "1": "Set Interference Area" + "3": "Clear Interference Area" + "0": "Factory Reset Module" + default: 3 + - name: "parameter112" + title: "112. mmWave Sensitivity" + description: "Adjust the sensitivity of the mmWave sensor. 0-Low, 1-Medium, 2-High." + required: false + preferenceType: enumeration + definition: + options: + "0": "Low" + "1": "Medium" + "2": "High (default)" + default: 2 + - name: "parameter113" + title: "113. mmWave Detection Delay" + description: "The time from detecting a person to triggering an action. 0-Low (5s), 1-Medium (1s), 2-Fast (0.2s)." + required: false + preferenceType: enumeration + definition: + options: + "0": "5 seconds" + "1": "1 second" + "2": "0.2 seconds (default)" + default: 2 + - name: "parameter114" + title: "mmWave Detection Timeout" + description: "Adjust the timeout after presence is no longer detected. After the timeout the load will turn off." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 4294967296 + default: 30 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/profiles/rgbw-bulb.yml b/drivers/SmartThings/zwave-switch/profiles/rgbw-bulb.yml new file mode 100644 index 0000000000..d87d32adc6 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/profiles/rgbw-bulb.yml @@ -0,0 +1,16 @@ +name: rgbw-bulb +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - id: colorTemperature + version: 1 + - id: colorControl + version: 1 + - id: refresh + version: 1 + categories: + - name: Light diff --git a/drivers/SmartThings/zwave-switch/src/aeon-smart-strip/can_handle.lua b/drivers/SmartThings/zwave-switch/src/aeon-smart-strip/can_handle.lua new file mode 100644 index 0000000000..5efc0d1df0 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/aeon-smart-strip/can_handle.lua @@ -0,0 +1,24 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +--- Determine whether the passed device is Aeon smart strip +--- +--- @param driver Driver driver instance +--- @param device Device device isntance +--- @return boolean true if the device proper, else false +local function can_handle_aeon_smart_strip(opts, driver, device, ...) + local fingerprints = { + {mfr = 0x0086, prod = 0x0003, model = 0x000B}, -- Aeon Smart Strip DSC11-ZWUS + } + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("aeon-smart-strip") + return true, subdriver + end + end + return false +end + + +return can_handle_aeon_smart_strip diff --git a/drivers/SmartThings/zwave-switch/src/aeon-smart-strip/init.lua b/drivers/SmartThings/zwave-switch/src/aeon-smart-strip/init.lua index f3bafc0815..4bc16ba392 100644 --- a/drivers/SmartThings/zwave-switch/src/aeon-smart-strip/init.lua +++ b/drivers/SmartThings/zwave-switch/src/aeon-smart-strip/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -24,29 +13,10 @@ local Meter = (require "st.zwave.CommandClass.Meter")({ version = 3 }) --- @type st.zwave.CommandClass.SwitchBinary local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({ version = 2 }) -local AEON_SMART_STRIP_FINGERPRINTS = { - {mfr = 0x0086, prod = 0x0003, model = 0x000B}, -- Aeon Smart Strip DSC11-ZWUS -} - local ENERGY_UNIT_KWH = "kWh" local ENERGY_UNIT_KVAH = "kVAh" local POWER_UNIT_WATT = "W" ---- Determine whether the passed device is Aeon smart strip ---- ---- @param driver Driver driver instance ---- @param device Device device isntance ---- @return boolean true if the device proper, else false -local function can_handle_aeon_smart_strip(opts, driver, device, ...) - for _, fingerprint in ipairs(AEON_SMART_STRIP_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("aeon-smart-strip") - return true, subdriver - end - end - return false -end - local function binary_event_helper(self, device, cmd) local value = cmd.args.value and cmd.args.value or cmd.args.target_value local event = value == SwitchBinary.value.OFF_DISABLE and capabilities.switch.switch.off() or capabilities.switch.switch.on() @@ -108,7 +78,7 @@ local aeon_smart_strip = { [Meter.REPORT] = meter_report_handler } }, - can_handle = can_handle_aeon_smart_strip, + can_handle = require("aeon-smart-strip.can_handle"), } return aeon_smart_strip diff --git a/drivers/SmartThings/zwave-switch/src/aeotec-heavy-duty/can_handle.lua b/drivers/SmartThings/zwave-switch/src/aeotec-heavy-duty/can_handle.lua new file mode 100644 index 0000000000..7c5995ea76 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/aeotec-heavy-duty/can_handle.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle(opts, driver, device, ...) + local fingerprints = { + { mfr = 0x0086, model = 0x004E } + } + + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, nil, fingerprint.model) then + local subdriver = require("aeotec-heavy-duty") + return true, subdriver + end + end + return false +end + +return can_handle diff --git a/drivers/SmartThings/zwave-switch/src/aeotec-heavy-duty/init.lua b/drivers/SmartThings/zwave-switch/src/aeotec-heavy-duty/init.lua index 9a10b32c64..2a47c4e122 100644 --- a/drivers/SmartThings/zwave-switch/src/aeotec-heavy-duty/init.lua +++ b/drivers/SmartThings/zwave-switch/src/aeotec-heavy-duty/init.lua @@ -1,18 +1,5 @@ --- Author: CommanderQ --- --- Copyright 2021 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass.Meter @@ -29,20 +16,6 @@ local LAST_REPORT_TIME = "LAST_REPORT_TIME" local POWER_UNIT_WATT = "W" local ENERGY_UNIT_KWH = "kWh" -local FINGERPRINTS = { - { mfr = 0x0086, model = 0x004E } -} - -local function can_handle(opts, driver, device, ...) - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match(fingerprint.mfr, nil, fingerprint.model) then - local subdriver = require("aeotec-heavy-duty") - return true, subdriver - end - end - return false -end - local function emit_power_consumption_report_event(device, value, channel) -- powerConsumptionReport report interval local current_time = os.time() @@ -128,7 +101,7 @@ local driver_template = { lifecycle_handlers = { infoChanged = info_changed }, - can_handle = can_handle + can_handle = require("aeotec-heavy-duty.can_handle") } -return driver_template; \ No newline at end of file +return driver_template; diff --git a/drivers/SmartThings/zwave-switch/src/aeotec-smart-switch/can_handle.lua b/drivers/SmartThings/zwave-switch/src/aeotec-smart-switch/can_handle.lua new file mode 100644 index 0000000000..b8a5c5587c --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/aeotec-smart-switch/can_handle.lua @@ -0,0 +1,21 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local function can_handle(opts, driver, device, ...) + local fingerprints = { + {mfr = 0x0086, prodId = 0x0060}, + {mfr = 0x0371, prodId = 0x00AF}, -- Smart Switch 7 EU + {mfr = 0x0371, prodId = 0x0017} -- Smart Switch 7 US + } + + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, nil, fingerprint.prodId) then + local subdriver = require("aeotec-smart-switch") + return true, subdriver + end + end + return false +end + +return can_handle diff --git a/drivers/SmartThings/zwave-switch/src/aeotec-smart-switch/init.lua b/drivers/SmartThings/zwave-switch/src/aeotec-smart-switch/init.lua index d014188b46..0b34f1ba85 100644 --- a/drivers/SmartThings/zwave-switch/src/aeotec-smart-switch/init.lua +++ b/drivers/SmartThings/zwave-switch/src/aeotec-smart-switch/init.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local Basic = (require "st.zwave.CommandClass.Basic")({ version=1 }) @@ -28,22 +17,6 @@ local LAST_REPORT_TIME = "LAST_REPORT_TIME" local POWER_UNIT_WATT = "W" local ENERGY_UNIT_KWH = "kWh" -local FINGERPRINTS = { - {mfr = 0x0086, prodId = 0x0060}, - {mfr = 0x0371, prodId = 0x00AF}, -- Smart Switch 7 EU - {mfr = 0x0371, prodId = 0x0017} -- Smart Switch 7 US -} - -local function can_handle(opts, driver, device, ...) - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:id_match(fingerprint.mfr, nil, fingerprint.prodId) then - local subdriver = require("aeotec-smart-switch") - return true, subdriver - end - end - return false -end - local function emit_power_consumption_report_event(device, value, channel) -- powerConsumptionReport report interval local current_time = os.time() @@ -144,7 +117,7 @@ local aeotec_smart_switch = { [Meter.REPORT] = meter_report_handler } }, - can_handle = can_handle + can_handle = require("aeotec-smart-switch.can_handle") } return aeotec_smart_switch diff --git a/drivers/SmartThings/zwave-switch/src/configurations.lua b/drivers/SmartThings/zwave-switch/src/configurations.lua index b2c283bd7c..199cf03237 100644 --- a/drivers/SmartThings/zwave-switch/src/configurations.lua +++ b/drivers/SmartThings/zwave-switch/src/configurations.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local devices = { AEOTEC_METERING_SWITCH = { diff --git a/drivers/SmartThings/zwave-switch/src/dawon-smart-plug/can_handle.lua b/drivers/SmartThings/zwave-switch/src/dawon-smart-plug/can_handle.lua new file mode 100644 index 0000000000..86fb4d7e68 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/dawon-smart-plug/can_handle.lua @@ -0,0 +1,24 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +--- Determine whether the passed device is Dawon smart plug +--- +--- @param driver Driver driver instance +--- @param device Device device isntance +--- @return boolean true if the device proper, else false +local function can_handle_dawon_smart_plug(opts, driver, device, ...) + local fingerprints = { + {mfr = 0x018C, prod = 0x0042, model = 0x0005}, -- Dawon Smart Plug + {mfr = 0x018C, prod = 0x0042, model = 0x0008} -- Dawon Smart Multitab + } + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("dawon-smart-plug") + return true, subdriver + end + end + return false +end + +return can_handle_dawon_smart_plug diff --git a/drivers/SmartThings/zwave-switch/src/dawon-smart-plug/init.lua b/drivers/SmartThings/zwave-switch/src/dawon-smart-plug/init.lua index 6280842f6b..84643a500f 100644 --- a/drivers/SmartThings/zwave-switch/src/dawon-smart-plug/init.lua +++ b/drivers/SmartThings/zwave-switch/src/dawon-smart-plug/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -18,25 +7,7 @@ local cc = require "st.zwave.CommandClass" --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) -local DAWON_SMART_PLUG_FINGERPRINTS = { - {mfr = 0x018C, prod = 0x0042, model = 0x0005}, -- Dawon Smart Plug - {mfr = 0x018C, prod = 0x0042, model = 0x0008} -- Dawon Smart Multitab -} ---- Determine whether the passed device is Dawon smart plug ---- ---- @param driver Driver driver instance ---- @param device Device device isntance ---- @return boolean true if the device proper, else false -local function can_handle_dawon_smart_plug(opts, driver, device, ...) - for _, fingerprint in ipairs(DAWON_SMART_PLUG_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("dawon-smart-plug") - return true, subdriver - end - end - return false -end --- Default handler for notification reports --- @@ -60,7 +31,7 @@ local dawon_smart_plug = { [Notification.REPORT] = notification_report_handler } }, - can_handle = can_handle_dawon_smart_plug + can_handle = require("dawon-smart-plug.can_handle") } return dawon_smart_plug diff --git a/drivers/SmartThings/zwave-switch/src/dawon-wall-smart-switch/can_handle.lua b/drivers/SmartThings/zwave-switch/src/dawon-wall-smart-switch/can_handle.lua new file mode 100644 index 0000000000..cad81076d1 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/dawon-wall-smart-switch/can_handle.lua @@ -0,0 +1,21 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +--- Determine whether the passed device is Dawon wall smart switch +--- +--- @param driver Driver driver instance +--- @param device Device device isntance +--- @return boolean true if the device proper, else false +local function can_handle_dawon_wall_smart_switch(opts, driver, device, ...) + local fingerprints = require("dawon-wall-smart-switch.fingerprints") + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("dawon-wall-smart-switch") + return true, subdriver + end + end + return false +end + +return can_handle_dawon_wall_smart_switch diff --git a/drivers/SmartThings/zwave-switch/src/dawon-wall-smart-switch/fingerprints.lua b/drivers/SmartThings/zwave-switch/src/dawon-wall-smart-switch/fingerprints.lua new file mode 100644 index 0000000000..ab6633bac4 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/dawon-wall-smart-switch/fingerprints.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + {mfr = 0x018C, prod = 0x0061, model = 0x0001}, -- Dawon Multipurpose Sensor + Smart Switch endpoint 1 KR + {mfr = 0x018C, prod = 0x0062, model = 0x0001}, -- Dawon Multipurpose Sensor + Smart Switch endpoint 2 KR + {mfr = 0x018C, prod = 0x0063, model = 0x0001}, -- Dawon Multipurpose Sensor + Smart Switch endpoint 3 KR + {mfr = 0x018C, prod = 0x0064, model = 0x0001}, -- Dawon Multipurpose Sensor + Smart Switch endpoint 1 US + {mfr = 0x018C, prod = 0x0065, model = 0x0001}, -- Dawon Multipurpose Sensor + Smart Switch endpoint 2 US + {mfr = 0x018C, prod = 0x0066, model = 0x0001} -- Dawon Multipurpose Sensor + Smart Switch endpoint 3 US +} diff --git a/drivers/SmartThings/zwave-switch/src/dawon-wall-smart-switch/init.lua b/drivers/SmartThings/zwave-switch/src/dawon-wall-smart-switch/init.lua index f7978ad54d..4f4278c057 100644 --- a/drivers/SmartThings/zwave-switch/src/dawon-wall-smart-switch/init.lua +++ b/drivers/SmartThings/zwave-switch/src/dawon-wall-smart-switch/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -24,30 +13,6 @@ local Notification = (require "st.zwave.CommandClass.Notification")({ version = --- @type st.zwave.CommandClass.SensorMultilevel local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ version = 5 }) -local DAWON_WALL_SMART_SWITCH_FINGERPRINTS = { - {mfr = 0x018C, prod = 0x0061, model = 0x0001}, -- Dawon Multipurpose Sensor + Smart Switch endpoint 1 KR - {mfr = 0x018C, prod = 0x0062, model = 0x0001}, -- Dawon Multipurpose Sensor + Smart Switch endpoint 2 KR - {mfr = 0x018C, prod = 0x0063, model = 0x0001}, -- Dawon Multipurpose Sensor + Smart Switch endpoint 3 KR - {mfr = 0x018C, prod = 0x0064, model = 0x0001}, -- Dawon Multipurpose Sensor + Smart Switch endpoint 1 US - {mfr = 0x018C, prod = 0x0065, model = 0x0001}, -- Dawon Multipurpose Sensor + Smart Switch endpoint 2 US - {mfr = 0x018C, prod = 0x0066, model = 0x0001} -- Dawon Multipurpose Sensor + Smart Switch endpoint 3 US -} - ---- Determine whether the passed device is Dawon wall smart switch ---- ---- @param driver Driver driver instance ---- @param device Device device isntance ---- @return boolean true if the device proper, else false -local function can_handle_dawon_wall_smart_switch(opts, driver, device, ...) - for _, fingerprint in ipairs(DAWON_WALL_SMART_SWITCH_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("dawon-wall-smart-switch") - return true, subdriver - end - end - return false -end - --- Default handler for notification reports --- --- @param self st.zwave.Driver @@ -97,7 +62,7 @@ local dawon_wall_smart_switch = { doConfigure = do_configure, infoChanged = info_changed }, - can_handle = can_handle_dawon_wall_smart_switch, + can_handle = require("dawon-wall-smart-switch.can_handle"), } return dawon_wall_smart_switch diff --git a/drivers/SmartThings/zwave-switch/src/eaton-5-scene-keypad/can_handle.lua b/drivers/SmartThings/zwave-switch/src/eaton-5-scene-keypad/can_handle.lua new file mode 100644 index 0000000000..274676cbed --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/eaton-5-scene-keypad/can_handle.lua @@ -0,0 +1,19 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + + +local function can_handle_eaton_5_scene_keypad(opts, driver, device, ...) + local fingerprints = { + {mfr = 0x001A, prod = 0x574D, model = 0x0000}, -- Eaton 5-Scene Keypad + } + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("eaton-5-scene-keypad") + return true, subdriver + end + end + return false +end + +return can_handle_eaton_5_scene_keypad diff --git a/drivers/SmartThings/zwave-switch/src/eaton-5-scene-keypad/init.lua b/drivers/SmartThings/zwave-switch/src/eaton-5-scene-keypad/init.lua index 29fccc9261..f8315135fe 100644 --- a/drivers/SmartThings/zwave-switch/src/eaton-5-scene-keypad/init.lua +++ b/drivers/SmartThings/zwave-switch/src/eaton-5-scene-keypad/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 --- @type st.capabilities local capabilities = require "st.capabilities" @@ -29,10 +18,6 @@ local SceneControllerConf = (require "st.zwave.CommandClass.SceneControllerConf" local INDICATOR_SWITCH_STATES = "Indicator_switch_states" -local EATON_5_SCENE_KEYPAD_FINGERPRINT = { - {mfr = 0x001A, prod = 0x574D, model = 0x0000}, -- Eaton 5-Scene Keypad -} - local function upsert_after_bit_update_at_index(device, bit_position, new_bit) local old_value = device:get_field(INDICATOR_SWITCH_STATES) or 0 local mask = ~(0x1 << (bit_position - 1)) @@ -108,16 +93,6 @@ local function do_configure(self, device) device:set_field(INDICATOR_SWITCH_STATES, 0, { persist = true}) end -local function can_handle_eaton_5_scene_keypad(opts, driver, device, ...) - for _, fingerprint in ipairs(EATON_5_SCENE_KEYPAD_FINGERPRINT) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("eaton-5-scene-keypad") - return true, subdriver - end - end - return false -end - local eaton_5_scene_keypad = { NAME = "Eaton 5-Scene Keypad", zwave_handlers = { @@ -146,7 +121,7 @@ local eaton_5_scene_keypad = { lifecycle_handlers = { doConfigure = do_configure, }, - can_handle = can_handle_eaton_5_scene_keypad, + can_handle = require("eaton-5-scene-keypad.can_handle"), } return eaton_5_scene_keypad diff --git a/drivers/SmartThings/zwave-switch/src/eaton-accessory-dimmer/can_handle.lua b/drivers/SmartThings/zwave-switch/src/eaton-accessory-dimmer/can_handle.lua new file mode 100644 index 0000000000..6b608b042b --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/eaton-accessory-dimmer/can_handle.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local function can_handle_eaton_accessory_dimmer(opts, driver, device, ...) + local fingerprints = { + {mfr = 0x001A, prod = 0x4441, model = 0x0000} -- Eaton Dimmer Switch + } + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("eaton-accessory-dimmer") + return true, subdriver + end + end + return false +end + +return can_handle_eaton_accessory_dimmer diff --git a/drivers/SmartThings/zwave-switch/src/eaton-accessory-dimmer/init.lua b/drivers/SmartThings/zwave-switch/src/eaton-accessory-dimmer/init.lua index 3a9d5c9a47..bb49665075 100644 --- a/drivers/SmartThings/zwave-switch/src/eaton-accessory-dimmer/init.lua +++ b/drivers/SmartThings/zwave-switch/src/eaton-accessory-dimmer/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" --- @type st.utils @@ -24,20 +13,6 @@ local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) --- @type st.zwave.CommandClass.SwitchMultilevel local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({ version = 4 }) -local EATON_ACCESSORY_DIMMER_FINGERPRINTS = { - {mfr = 0x001A, prod = 0x4441, model = 0x0000} -- Eaton Dimmer Switch -} - -local function can_handle_eaton_accessory_dimmer(opts, driver, device, ...) - for _, fingerprint in ipairs(EATON_ACCESSORY_DIMMER_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("eaton-accessory-dimmer") - return true, subdriver - end - end - return false -end - local function dimmer_event(driver, device, cmd) local level = cmd.args.value and cmd.args.value or cmd.args.target_value @@ -110,7 +85,7 @@ local eaton_accessory_dimmer = { [capabilities.switchLevel.commands.setLevel.NAME] = switch_level_set } }, - can_handle = can_handle_eaton_accessory_dimmer, + can_handle = require("eaton-accessory-dimmer.can_handle"), } return eaton_accessory_dimmer diff --git a/drivers/SmartThings/zwave-switch/src/eaton-anyplace-switch/can_handle.lua b/drivers/SmartThings/zwave-switch/src/eaton-anyplace-switch/can_handle.lua new file mode 100644 index 0000000000..67aadecb8b --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/eaton-anyplace-switch/can_handle.lua @@ -0,0 +1,19 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + + +local function can_handle_eaton_anyplace_switch(opts, driver, device, ...) + local fingerprints = { + { manufacturerId = 0x001A, productType = 0x4243, productId = 0x0000 } -- Eaton Anyplace Switch + } + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("eaton-anyplace-switch") + return true, subdriver + end + end + return false +end + +return can_handle_eaton_anyplace_switch diff --git a/drivers/SmartThings/zwave-switch/src/eaton-anyplace-switch/init.lua b/drivers/SmartThings/zwave-switch/src/eaton-anyplace-switch/init.lua index b2110623f6..488babc276 100644 --- a/drivers/SmartThings/zwave-switch/src/eaton-anyplace-switch/init.lua +++ b/drivers/SmartThings/zwave-switch/src/eaton-anyplace-switch/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -20,20 +9,6 @@ local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) local switch_utils = require "switch_utils" -local EATON_ANYPLACE_SWITCH_FINGERPRINTS = { - { manufacturerId = 0x001A, productType = 0x4243, productId = 0x0000 } -- Eaton Anyplace Switch -} - -local function can_handle_eaton_anyplace_switch(opts, driver, device, ...) - for _, fingerprint in ipairs(EATON_ANYPLACE_SWITCH_FINGERPRINTS) do - if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("eaton-anyplace-switch") - return true, subdriver - end - end - return false -end - local function basic_set_handler(self, device, cmd) if cmd.args.value == 0xFF then device:emit_event(capabilities.switch.switch.on()) @@ -76,7 +51,7 @@ local eaton_anyplace_switch = { lifecycle_handlers = { added = device_added }, - can_handle = can_handle_eaton_anyplace_switch + can_handle = require("eaton-anyplace-switch.can_handle") } return eaton_anyplace_switch diff --git a/drivers/SmartThings/zwave-switch/src/ecolink-switch/can_handle.lua b/drivers/SmartThings/zwave-switch/src/ecolink-switch/can_handle.lua new file mode 100644 index 0000000000..87771168f4 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/ecolink-switch/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local function can_handle_ecolink(opts, driver, device, ...) + local fingerprints = require("ecolink-switch.fingerprints") + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("ecolink-switch") + return true, subdriver + end + end + return false +end + +return can_handle_ecolink diff --git a/drivers/SmartThings/zwave-switch/src/ecolink-switch/fingerprints.lua b/drivers/SmartThings/zwave-switch/src/ecolink-switch/fingerprints.lua new file mode 100644 index 0000000000..742590dc1d --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/ecolink-switch/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + {mfr = 0x014A, prod = 0x0006, model = 0x0002}, + {mfr = 0x014A, prod = 0x0006, model = 0x0003}, + {mfr = 0x014A, prod = 0x0006, model = 0x0004}, + {mfr = 0x014A, prod = 0x0006, model = 0x0005}, + {mfr = 0x014A, prod = 0x0006, model = 0x0006} +} diff --git a/drivers/SmartThings/zwave-switch/src/ecolink-switch/init.lua b/drivers/SmartThings/zwave-switch/src/ecolink-switch/init.lua index 3e69399801..081cf113dd 100644 --- a/drivers/SmartThings/zwave-switch/src/ecolink-switch/init.lua +++ b/drivers/SmartThings/zwave-switch/src/ecolink-switch/init.lua @@ -1,39 +1,10 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local cc = require "st.zwave.CommandClass" local Basic = (require "st.zwave.CommandClass.Basic")({ version=1 }) -local ECOLINK_FINGERPRINTS = { - {mfr = 0x014A, prod = 0x0006, model = 0x0002}, - {mfr = 0x014A, prod = 0x0006, model = 0x0003}, - {mfr = 0x014A, prod = 0x0006, model = 0x0004}, - {mfr = 0x014A, prod = 0x0006, model = 0x0005}, - {mfr = 0x014A, prod = 0x0006, model = 0x0006} -} - -local function can_handle_ecolink(opts, driver, device, ...) - for _, fingerprint in ipairs(ECOLINK_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("ecolink-switch") - return true, subdriver - end - end - return false -end - local function basic_set_handler(driver, device, cmd) if cmd.args.value == 0xFF then device:emit_event(capabilities.switch.switch.on()) @@ -49,7 +20,7 @@ local ecolink_switch = { [Basic.SET] = basic_set_handler } }, - can_handle = can_handle_ecolink + can_handle = require("ecolink-switch.can_handle") } return ecolink_switch diff --git a/drivers/SmartThings/zwave-switch/src/fibaro-double-switch/can_handle.lua b/drivers/SmartThings/zwave-switch/src/fibaro-double-switch/can_handle.lua new file mode 100644 index 0000000000..5645551e3b --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/fibaro-double-switch/can_handle.lua @@ -0,0 +1,21 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local function can_handle_fibaro_double_switch(opts, driver, device, ...) + local fingerprints = { + {mfr = 0x010F, prod = 0x0203, model = 0x1000}, -- Fibaro Switch + {mfr = 0x010F, prod = 0x0203, model = 0x2000}, -- Fibaro Switch + {mfr = 0x010F, prod = 0x0203, model = 0x3000} -- Fibaro Switch + } + + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("fibaro-double-switch") + return true, subdriver + end + end + return false +end + +return can_handle_fibaro_double_switch diff --git a/drivers/SmartThings/zwave-switch/src/fibaro-double-switch/init.lua b/drivers/SmartThings/zwave-switch/src/fibaro-double-switch/init.lua index 8bccc75464..e02d986b91 100644 --- a/drivers/SmartThings/zwave-switch/src/fibaro-double-switch/init.lua +++ b/drivers/SmartThings/zwave-switch/src/fibaro-double-switch/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local st_device = require "st.device" local capabilities = require "st.capabilities" @@ -34,22 +23,6 @@ local ENDPOINTS = { child = 2 } -local FIBARO_DOUBLE_SWITCH_FINGERPRINTS = { - {mfr = 0x010F, prod = 0x0203, model = 0x1000}, -- Fibaro Switch - {mfr = 0x010F, prod = 0x0203, model = 0x2000}, -- Fibaro Switch - {mfr = 0x010F, prod = 0x0203, model = 0x3000} -- Fibaro Switch -} - -local function can_handle_fibaro_double_switch(opts, driver, device, ...) - for _, fingerprint in ipairs(FIBARO_DOUBLE_SWITCH_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("fibaro-double-switch") - return true, subdriver - end - end - return false -end - local function do_refresh(driver, device, command) local component = command and command.component and command.component or "main" device:send_to_component(SwitchBinary:Get({}), component) @@ -140,7 +113,7 @@ local fibaro_double_switch = { init = device_init, added = device_added }, - can_handle = can_handle_fibaro_double_switch, + can_handle = require("fibaro-double-switch.can_handle") } return fibaro_double_switch diff --git a/drivers/SmartThings/zwave-switch/src/fibaro-single-switch/can_handle.lua b/drivers/SmartThings/zwave-switch/src/fibaro-single-switch/can_handle.lua new file mode 100644 index 0000000000..80d41e924b --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/fibaro-single-switch/can_handle.lua @@ -0,0 +1,20 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local function can_handle_fibaro_single_switch(opts, driver, device, ...) + local fingerprints = { + {mfr = 0x010F, prod = 0x0403, model = 0x1000}, -- Fibaro Switch + {mfr = 0x010F, prod = 0x0403, model = 0x2000}, -- Fibaro Switch + {mfr = 0x010F, prod = 0x0403, model = 0x3000} -- Fibaro Switch + } + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("fibaro-single-switch") + return true, subdriver + end + end + return false +end + +return can_handle_fibaro_single_switch diff --git a/drivers/SmartThings/zwave-switch/src/fibaro-single-switch/init.lua b/drivers/SmartThings/zwave-switch/src/fibaro-single-switch/init.lua index e36703ccd6..8a3252cc32 100644 --- a/drivers/SmartThings/zwave-switch/src/fibaro-single-switch/init.lua +++ b/drivers/SmartThings/zwave-switch/src/fibaro-single-switch/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" local ButtonDefaults = require "st.zwave.defaults.button" @@ -28,22 +17,6 @@ local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({ version = --- @type st.zwave.CommandClass.Basic local Basic = (require "st.zwave.CommandClass.Basic")({ version=1 }) -local FIBARO_SINGLE_SWITCH_FINGERPRINTS = { - {mfr = 0x010F, prod = 0x0403, model = 0x1000}, -- Fibaro Switch - {mfr = 0x010F, prod = 0x0403, model = 0x2000}, -- Fibaro Switch - {mfr = 0x010F, prod = 0x0403, model = 0x3000} -- Fibaro Switch -} - -local function can_handle_fibaro_single_switch(opts, driver, device, ...) - for _, fingerprint in ipairs(FIBARO_SINGLE_SWITCH_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("fibaro-single-switch") - return true, subdriver - end - end - return false -end - local function central_scene_notification_handler(self, device, cmd) if cmd.src_channel == nil or cmd.src_channel == 0 then ButtonDefaults.zwave_handlers[cc.CENTRAL_SCENE][CentralScene.NOTIFICATION](self, device, cmd) @@ -94,7 +67,7 @@ local fibaro_single_switch = { [capabilities.switch.commands.off.NAME] = switch_handler_factory(0x00), } }, - can_handle = can_handle_fibaro_single_switch, + can_handle = require("fibaro-single-switch.can_handle") } return fibaro_single_switch diff --git a/drivers/SmartThings/zwave-switch/src/fibaro-wall-plug-us/can_handle.lua b/drivers/SmartThings/zwave-switch/src/fibaro-wall-plug-us/can_handle.lua new file mode 100644 index 0000000000..2a4c8f5b40 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/fibaro-wall-plug-us/can_handle.lua @@ -0,0 +1,19 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local function can_handle_fibaro_wall_plug(opts, driver, device, ...) + local fingerprints = { + {mfr = 0x010F, prod = 0x1401, model = 0x1001}, -- Fibaro Outlet + {mfr = 0x010F, prod = 0x1401, model = 0x2000}, -- Fibaro Outlet + } + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("fibaro-wall-plug-us") + return true, subdriver + end + end + return false +end + +return can_handle_fibaro_wall_plug diff --git a/drivers/SmartThings/zwave-switch/src/fibaro-wall-plug-us/init.lua b/drivers/SmartThings/zwave-switch/src/fibaro-wall-plug-us/init.lua index 6224421c75..8800e029ab 100644 --- a/drivers/SmartThings/zwave-switch/src/fibaro-wall-plug-us/init.lua +++ b/drivers/SmartThings/zwave-switch/src/fibaro-wall-plug-us/init.lua @@ -1,31 +1,5 @@ --- Copyright 2022 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 FIBARO_WALL_PLUG_FINGERPRINTS = { - {mfr = 0x010F, prod = 0x1401, model = 0x1001}, -- Fibaro Outlet - {mfr = 0x010F, prod = 0x1401, model = 0x2000}, -- Fibaro Outlet -} - -local function can_handle_fibaro_wall_plug(opts, driver, device, ...) - for _, fingerprint in ipairs(FIBARO_WALL_PLUG_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("fibaro-wall-plug-us") - return true, subdriver - end - end - return false -end +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local function component_to_endpoint(device, component_id) if component_id == "main" then @@ -54,7 +28,7 @@ local fibaro_wall_plug = { lifecycle_handlers = { init = device_init }, - can_handle = can_handle_fibaro_wall_plug, + can_handle = require("fibaro-wall-plug-us.can_handle"), } return fibaro_wall_plug diff --git a/drivers/SmartThings/zwave-switch/src/init.lua b/drivers/SmartThings/zwave-switch/src/init.lua index 3acae8ffe0..405600e962 100644 --- a/drivers/SmartThings/zwave-switch/src/init.lua +++ b/drivers/SmartThings/zwave-switch/src/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" --- @type st.zwave.defaults @@ -28,6 +17,8 @@ local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({ ve local preferencesMap = require "preferences" local configurationsMap = require "configurations" +local lazy_load_if_possible = require "lazy_load_subdriver" + --- Map component to end_points(channels) --- --- @param device st.zwave.Device @@ -103,19 +94,6 @@ local function switch_multilevel_stop_level_change_handler(driver, device, cmd) device:send(SwitchMultilevel:Get({})) end -local function lazy_load_if_possible(sub_driver_name) - -- gets the current lua libs api version - local version = require "version" - - -- version 9 will include the lazy loading functions - if version.api >= 9 then - return ZwaveDriver.lazy_load_sub_driver(require(sub_driver_name)) - else - return require(sub_driver_name) - end - -end - ------------------------------------------------------------------------------------------- -- Register message handlers and run driver ------------------------------------------------------------------------------------------- @@ -144,7 +122,7 @@ local driver_template = { }, sub_drivers = { lazy_load_if_possible("eaton-accessory-dimmer"), - lazy_load_if_possible("inovelli-LED"), + lazy_load_if_possible("inovelli"), lazy_load_if_possible("dawon-smart-plug"), lazy_load_if_possible("inovelli-2-channel-smart-plug"), lazy_load_if_possible("zwave-dual-switch"), diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-2-channel-smart-plug/can_handle.lua b/drivers/SmartThings/zwave-switch/src/inovelli-2-channel-smart-plug/can_handle.lua new file mode 100644 index 0000000000..2bc88d57c5 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/inovelli-2-channel-smart-plug/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local function can_handle_inovelli_2_channel_smart_plug(opts, driver, device, ...) + local fingerprints = require("inovelli-2-channel-smart-plug.fingerprints") + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("inovelli-2-channel-smart-plug") + return true, subdriver + end + end + return false +end + +return can_handle_inovelli_2_channel_smart_plug diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-2-channel-smart-plug/fingerprints.lua b/drivers/SmartThings/zwave-switch/src/inovelli-2-channel-smart-plug/fingerprints.lua new file mode 100644 index 0000000000..ab2150468d --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/inovelli-2-channel-smart-plug/fingerprints.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + {mfr = 0x015D, prod = 0x0221, model = 0x251C}, -- Show Home Outlet + {mfr = 0x0312, prod = 0x0221, model = 0x251C}, -- Inovelli Outlet + {mfr = 0x0312, prod = 0xB221, model = 0x251C}, -- Inovelli Outlet + {mfr = 0x0312, prod = 0x0221, model = 0x611C}, -- Inovelli Outlet + {mfr = 0x015D, prod = 0x0221, model = 0x611C}, -- Inovelli Outlet + {mfr = 0x015D, prod = 0x6100, model = 0x6100}, -- Inovelli Outlet + {mfr = 0x0312, prod = 0x6100, model = 0x6100}, -- Inovelli Outlet + {mfr = 0x015D, prod = 0x2500, model = 0x2500}, -- Inovelli Outlet +} diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-2-channel-smart-plug/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-2-channel-smart-plug/init.lua index d97f85063c..313280d131 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli-2-channel-smart-plug/init.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli-2-channel-smart-plug/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -24,27 +13,6 @@ local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({ version = --- @type st.zwave.CommandClass.Association local Association = (require "st.zwave.CommandClass.Association")({ version = 1 }) -local INOVELLI_2_CHANNEL_SMART_PLUG_FINGERPRINTS = { - {mfr = 0x015D, prod = 0x0221, model = 0x251C}, -- Show Home Outlet - {mfr = 0x0312, prod = 0x0221, model = 0x251C}, -- Inovelli Outlet - {mfr = 0x0312, prod = 0xB221, model = 0x251C}, -- Inovelli Outlet - {mfr = 0x0312, prod = 0x0221, model = 0x611C}, -- Inovelli Outlet - {mfr = 0x015D, prod = 0x0221, model = 0x611C}, -- Inovelli Outlet - {mfr = 0x015D, prod = 0x6100, model = 0x6100}, -- Inovelli Outlet - {mfr = 0x0312, prod = 0x6100, model = 0x6100}, -- Inovelli Outlet - {mfr = 0x015D, prod = 0x2500, model = 0x2500}, -- Inovelli Outlet -} - -local function can_handle_inovelli_2_channel_smart_plug(opts, driver, device, ...) - for _, fingerprint in ipairs(INOVELLI_2_CHANNEL_SMART_PLUG_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("inovelli-2-channel-smart-plug") - return true, subdriver - end - end - return false -end - local function handle_main_switch_event(device, value) if value == SwitchBinary.value.ON_ENABLE then device:emit_event(capabilities.switch.switch.on()) @@ -125,7 +93,7 @@ local inovelli_2_channel_smart_plug = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_inovelli_2_channel_smart_plug, + can_handle = require("inovelli-2-channel-smart-plug.can_handle") } return inovelli_2_channel_smart_plug diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-LED/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-LED/init.lua deleted file mode 100644 index 36bf1e0716..0000000000 --- a/drivers/SmartThings/zwave-switch/src/inovelli-LED/init.lua +++ /dev/null @@ -1,111 +0,0 @@ --- Copyright 2022 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" ---- @type st.utils -local utils = require "st.utils" ---- @type st.zwave.constants -local constants = require "st.zwave.constants" ---- @type st.zwave.CommandClass -local cc = require "st.zwave.CommandClass" ---- @type st.zwave.CommandClass.Configuration -local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) - -local LED_COLOR_CONTROL_PARAMETER_NUMBER = 13 -local LED_GENERIC_SATURATION = 100 -local INOVELLI_MANUFACTURER_ID = 0x031E -local INOVELLI_LZW31SN_PRODUCT_TYPE = 0x0001 -local INOVELLI_LZW31_PRODUCT_TYPE = 0x0003 -local INOVELLI_DIMMER_PRODUCT_ID = 0x0001 -local LED_BAR_COMPONENT_NAME = "LEDColorConfiguration" - -local function huePercentToZwaveValue(value) - if value <= 2 then - return 0 - elseif value >= 98 then - return 255 - else - return utils.round(value / 100 * 255) - end -end - -local function zwaveValueToHuePercent(value) - if value <= 2 then - return 0 - elseif value >= 254 then - return 100 - else - return utils.round(value / 255 * 100) - end -end - -local function configuration_report(driver, device, cmd) - if cmd.args.parameter_number == LED_COLOR_CONTROL_PARAMETER_NUMBER then - local hue = zwaveValueToHuePercent(cmd.args.configuration_value) - - local ledBarComponent = device.profile.components[LED_BAR_COMPONENT_NAME] - if ledBarComponent ~= nil then - device:emit_component_event(ledBarComponent, capabilities.colorControl.hue(hue)) - device:emit_component_event(ledBarComponent, capabilities.colorControl.saturation(LED_GENERIC_SATURATION)) - end - end -end - -local function set_color(driver, device, cmd) - local value = huePercentToZwaveValue(cmd.args.color.hue) - local config = Configuration:Set({ - parameter_number=LED_COLOR_CONTROL_PARAMETER_NUMBER, - configuration_value=value, - size=2 - }) - device:send(config) - - local query_configuration = function() - device:send(Configuration:Get({ parameter_number=LED_COLOR_CONTROL_PARAMETER_NUMBER })) - end - - device.thread:call_with_delay(constants.DEFAULT_GET_STATUS_DELAY, query_configuration) -end - -local function can_handle_inovelli_led(opts, driver, device, ...) - if device:id_match( - INOVELLI_MANUFACTURER_ID, - {INOVELLI_LZW31SN_PRODUCT_TYPE, INOVELLI_LZW31_PRODUCT_TYPE}, - INOVELLI_DIMMER_PRODUCT_ID - ) then - local subdriver = require("inovelli-LED") - return true, subdriver - end - return false -end - -local inovelli_led = { - NAME = "Inovelli LED", - zwave_handlers = { - [cc.CONFIGURATION] = { - [Configuration.REPORT] = configuration_report - } - }, - capability_handlers = { - [capabilities.colorControl.ID] = { - [capabilities.colorControl.commands.setColor.NAME] = set_color - } - }, - can_handle = can_handle_inovelli_led, - sub_drivers = { - require("inovelli-LED/inovelli-lzw31sn") - } -} - -return inovelli_led diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-LED/inovelli-lzw31sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-LED/inovelli-lzw31sn/init.lua deleted file mode 100644 index 307ee9b46a..0000000000 --- a/drivers/SmartThings/zwave-switch/src/inovelli-LED/inovelli-lzw31sn/init.lua +++ /dev/null @@ -1,112 +0,0 @@ --- Copyright 2022 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" ---- @type st.zwave.CommandClass -local cc = require "st.zwave.CommandClass" ---- @type st.zwave.CommandClass.CentralScene -local CentralScene = (require "st.zwave.CommandClass.CentralScene")({version=3}) - -local INOVELLI_MANUFACTURER_ID = 0x031E -local INOVELLI_LZW31SN_PRODUCT_TYPE = 0x0001 -local INOVELLI_DIMMER_PRODUCT_ID = 0x0001 -local LED_BAR_COMPONENT_NAME = "LEDColorConfiguration" - -local supported_button_values = { - ["button1"] = {"pushed", "pushed_2x", "pushed_3x", "pushed_4x", "pushed_5x"}, - ["button2"] = {"pushed", "pushed_2x", "pushed_3x", "pushed_4x", "pushed_5x"}, - ["button3"] = {"pushed"} -} - -local function device_added(driver, device) - for _, component in pairs(device.profile.components) do - if component.id ~= "main" and component.id ~= LED_BAR_COMPONENT_NAME then - device:emit_component_event( - component, - capabilities.button.supportedButtonValues( - supported_button_values[component.id], - { visibility = { displayed = false } } - ) - ) - device:emit_component_event( - component, - capabilities.button.numberOfButtons({value = 1}, { visibility = { displayed = false } }) - ) - end - end - device:refresh() -end - -local map_scene_number_to_component = { - [1] = "button2", - [2] = "button1", - [3] = "button3" -} - - -local map_key_attribute_to_capability = { - [CentralScene.key_attributes.KEY_PRESSED_1_TIME] = capabilities.button.button.pushed, - [CentralScene.key_attributes.KEY_PRESSED_2_TIMES] = capabilities.button.button.pushed_2x, - [CentralScene.key_attributes.KEY_PRESSED_3_TIMES] = capabilities.button.button.pushed_3x, - [CentralScene.key_attributes.KEY_PRESSED_4_TIMES] = capabilities.button.button.pushed_4x, - [CentralScene.key_attributes.KEY_PRESSED_5_TIMES] = capabilities.button.button.pushed_5x, -} - -local function central_scene_notification_handler(self, device, cmd) - if ( cmd.args.scene_number ~= nil and cmd.args.scene_number ~= 0 ) then - local capability_attribute = map_key_attribute_to_capability[cmd.args.key_attributes] - local additional_fields = { - state_change = true - } - - local event - if capability_attribute ~= nil then - event = capability_attribute(additional_fields) - end - - if event ~= nil then - -- device reports scene notifications from endpoint 0 (main) but central scene events have to be emitted for button components: 1,2,3 - local comp = device.profile.components[map_scene_number_to_component[cmd.args.scene_number]] - if comp ~= nil then - device:emit_component_event(comp, event) - end - end - end -end - -local function can_handle_inovelli_lzw31sn(opts, driver, device, ...) - if device:id_match( - INOVELLI_MANUFACTURER_ID, - INOVELLI_LZW31SN_PRODUCT_TYPE, - INOVELLI_DIMMER_PRODUCT_ID - ) then - return true - end - return false -end - -local inovelli_led_lzw31sn = { - NAME = "Inovelli LED LZW 31SN", - zwave_handlers = { - [cc.CENTRAL_SCENE] = { - [CentralScene.NOTIFICATION] = central_scene_notification_handler - } - }, - lifecycle_handlers = { - added = device_added - }, - can_handle = can_handle_inovelli_lzw31sn -} - -return inovelli_led_lzw31sn diff --git a/drivers/SmartThings/zwave-switch/src/inovelli/can_handle.lua b/drivers/SmartThings/zwave-switch/src/inovelli/can_handle.lua new file mode 100644 index 0000000000..7c0f6be77b --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/inovelli/can_handle.lua @@ -0,0 +1,20 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local INOVELLI_FINGERPRINTS = { + { mfr = 0x031E, prod = 0x0017, model = 0x0001 }, -- Inovelli VZW32-SN + { mfr = 0x031E, prod = 0x0001, model = 0x0001 }, -- Inovelli LZW31SN + { mfr = 0x031E, prod = 0x0003, model = 0x0001 }, -- Inovelli LZW31 +} + +local function can_handle_inovelli(opts, driver, device, ...) + for _, fingerprint in ipairs(INOVELLI_FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("inovelli") + return true, subdriver + end + end + return false +end + +return can_handle_inovelli diff --git a/drivers/SmartThings/zwave-switch/src/inovelli/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli/init.lua new file mode 100644 index 0000000000..84ed2c7df7 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/inovelli/init.lua @@ -0,0 +1,501 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +--- @type st.zwave.CommandClass.Configuration +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) +--- @type st.zwave.CommandClass.Association +local Association = (require "st.zwave.CommandClass.Association")({ version = 1 }) +--- @type st.zwave.CommandClass.SwitchBinary +local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({ version = 2 }) +--- @type st.zwave.CommandClass.Basic +local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) +--- @type st.zwave.CommandClass.SwitchMultilevel +local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({version=4}) +--- @type st.zwave.CommandClass.Meter +local Meter = (require "st.zwave.CommandClass.Meter")({ version = 3 }) +local preferencesMap = require "preferences" + +--- @type st.utils +local utils = require "st.utils" +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +local log = require "log" +local st_device = require "st.device" + +--- @type st.zwave.CommandClass.CentralScene +local CentralScene = (require "st.zwave.CommandClass.CentralScene")({version=3}) +--- @type st.zwave.constants +local constants = require "st.zwave.constants" + +local LATEST_CLOCK_SET_TIMESTAMP = "latest_clock_set_timestamp" + +local GEN3_NOTIFICATION_PARAMETER_NUMBER = 99 +local GEN2_NOTIFICATION_PARAMETER_NUMBER = 16 +local LED_COLOR_CONTROL_PARAMETER_NUMBER = 13 +local LED_BAR_COMPONENT_NAME = "LEDColorConfiguration" +local LED_GENERIC_SATURATION = 100 + +-- TODO: Remove after transition period - supportedButtonValues initialization +-- This table defines the supported button values for each button component. +-- Used to initialize supportedButtonValues on device_added and update devices with old values. +local supported_button_values = { + ["button1"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button2"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button3"] = {"pushed"} +} + +-- Device type detection helpers +local function is_gen2(device) + return device:id_match(0x031E, {0x0001, 0x0003}, 0x0001) +end + +local function is_gen3(device) + return device:id_match(0x031E, {0x0015, 0x0017}, 0x0001) +end + +-- Helper function to get the correct notification parameter number based on device type +local function get_notification_parameter_number(device) + -- For child devices, check the parent device type + local device_to_check = device + if device.network_type == st_device.NETWORK_TYPE_CHILD then + device_to_check = device:get_parent_device() + end + + if is_gen3(device_to_check) then + return GEN3_NOTIFICATION_PARAMETER_NUMBER + else + return GEN2_NOTIFICATION_PARAMETER_NUMBER + end +end + +local function button_to_component(buttonId) + if buttonId > 0 then + return string.format("button%d", buttonId) + end +end + +local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return utils.round(value / 100 * 255) + end +end + +local function valueToHuePercent(value) + if value <= 2 then + return 0 + elseif value >= 254 then + return 100 + else + return utils.round(value / 255 * 100) + end +end + +local preferences_to_numeric_value = function(new_value) + local numeric = tonumber(new_value) + if numeric == nil then -- in case the value is boolean + numeric = new_value and 1 or 0 + end + return numeric +end + +local preferences_calculate_parameter = function(new_value, type, number) + if type == 4 and new_value > 2147483647 then + return ((4294967296 - new_value) * -1) + elseif type == 2 and new_value > 32767 then + return ((65536 - new_value) * -1) + elseif type == 1 and new_value > 127 then + return ((256 - new_value) * -1) + else + return new_value + end +end + +local function add_child(driver,parent,profile,child_type) + local child_metadata = { + type = "EDGE_CHILD", + label = string.format("%s %s", parent.label, child_type:gsub("(%l)(%w*)", function(a,b) return string.upper(a)..b end)), + profile = profile, + parent_device_id = parent.id, + parent_assigned_child_key = child_type, + vendor_provided_label = string.format("%s %s", parent.label, child_type:gsub("(%l)(%w*)", function(a,b) return string.upper(a)..b end)) + } + driver:try_create_device(child_metadata) +end + +local function getNotificationValue(device, value) + local level = device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) or 100 + local color = utils.round(device:get_latest_state("main", capabilities.colorControl.ID, capabilities.colorControl.hue.NAME) or 100) + local effect = device:get_parent_device().preferences.notificationType or 1 + local duration = 255 -- Default duration + + -- Get the parent device to check generation for child devices + local device_to_check = device + if device.network_type == st_device.NETWORK_TYPE_CHILD then + device_to_check = device:get_parent_device() + end + + local colorValue = huePercentToValue(value or color) + local notificationValue = 0 + + if is_gen3(device_to_check) then + -- Gen3 order: duration, level, color, effect (bytes 0-3 from low to high) + notificationValue = notificationValue + (effect * 16777216) -- byte 3 (highest) + notificationValue = notificationValue + (colorValue * 65536) -- byte 2 + notificationValue = notificationValue + (level * 256) -- byte 1 + notificationValue = notificationValue + (duration * 1) -- byte 0 (lowest) + else + -- Gen2 order: color, level, duration, effect (bytes 0-3 from low to high) + notificationValue = notificationValue + (effect * 16777216) -- byte 3 (highest) + notificationValue = notificationValue + (duration * 65536) -- byte 2 + notificationValue = notificationValue + (level * 256) -- byte 1 + notificationValue = notificationValue + (colorValue * 1) -- byte 0 (lowest) + end + + return notificationValue +end + +local function set_color(driver, device, command) + if device.network_type == st_device.NETWORK_TYPE_CHILD then + device:emit_event(capabilities.colorControl.hue(command.args.color.hue)) + device:emit_event(capabilities.colorControl.saturation(command.args.color.saturation)) + device:emit_event(capabilities.switch.switch("on")) + local dev = device:get_parent_device() + local config = Configuration:Set({ + parameter_number=get_notification_parameter_number(device), + configuration_value=getNotificationValue(device), + size=4 + }) + local send_configuration = function() + dev:send(config) + end + device.thread:call_with_delay(1,send_configuration) + else + local value = huePercentToValue(command.args.color.hue) + local config = Configuration:Set({ + parameter_number=LED_COLOR_CONTROL_PARAMETER_NUMBER, + configuration_value=value, + size=2 + }) + device:send(config) + + local query_configuration = function() + device:send(Configuration:Get({ parameter_number=LED_COLOR_CONTROL_PARAMETER_NUMBER })) + end + + device.thread:call_with_delay(constants.DEFAULT_GET_STATUS_DELAY, query_configuration) + end +end + +local function set_color_temperature(driver, device, command) + if device.network_type == st_device.NETWORK_TYPE_CHILD then + device:emit_event(capabilities.colorControl.hue(100)) + device:emit_event(capabilities.colorTemperature.colorTemperature(command.args.temperature)) + device:emit_event(capabilities.switch.switch("on")) + local dev = device:get_parent_device() + local config = Configuration:Set({ + parameter_number=get_notification_parameter_number(device), + configuration_value=getNotificationValue(device, 100), + size=4 + }) + local send_configuration = function() + dev:send(config) + end + device.thread:call_with_delay(1,send_configuration) + else + local value = huePercentToValue(100) + local config = Configuration:Set({ + parameter_number=LED_COLOR_CONTROL_PARAMETER_NUMBER, + configuration_value=value, + size=2 + }) + device:send(config) + + local query_configuration = function() + device:send(Configuration:Get({ parameter_number=LED_COLOR_CONTROL_PARAMETER_NUMBER })) + end + + device.thread:call_with_delay(constants.DEFAULT_GET_STATUS_DELAY, query_configuration) + end +end + +local function switch_level_set(driver, device, command) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + local level = utils.round(command.args.level) + level = utils.clamp_value(level, 0, 99) + + device:send(SwitchMultilevel:Set({ value=level, duration=command.args.rate or "default" })) + + device.thread:call_with_delay(3, function(d) + device:send(SwitchMultilevel:Get({})) + end) + else + device:emit_event(capabilities.switchLevel.level(command.args.level)) + device:emit_event(capabilities.switch.switch(command.args.level ~= 0 and "on" or "off")) + local dev = device:get_parent_device() + local config = Configuration:Set({ + parameter_number=get_notification_parameter_number(device), + configuration_value=getNotificationValue(device), + size=4 + }) + local send_configuration = function() + dev:send(config) + end + device.thread:call_with_delay(1,send_configuration) + end +end + +local function refresh_handler(driver, device) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:send(SwitchMultilevel:Get({})) + device:send(Meter:Get({ scale = Meter.scale.electric_meter.WATTS })) + device:send(Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS })) + end +end + +local function device_added(driver, device) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:send(Association:Set({grouping_identifier = 1, node_ids = {driver.environment_info.hub_zwave_id}})) + refresh_handler(driver, device) + if is_gen2(device) then + local ledBarComponent = device.profile.components[LED_BAR_COMPONENT_NAME] + if ledBarComponent ~= nil then + device:emit_component_event(ledBarComponent, capabilities.colorControl.hue(1)) + device:emit_component_event(ledBarComponent, capabilities.colorControl.saturation(1)) + end + end + else + device:emit_event(capabilities.colorControl.hue(1)) + device:emit_event(capabilities.colorControl.saturation(1)) + device:emit_event(capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = 2700, maximum = 6500} })) + device:emit_event(capabilities.switchLevel.level(100)) + device:emit_event(capabilities.switch.switch("off")) + end +end + +local function info_changed(driver, device, event, args) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + local time_diff = 3 + local last_clock_set_time = device:get_field(LATEST_CLOCK_SET_TIMESTAMP) + if last_clock_set_time ~= nil then + time_diff = os.difftime(os.time(), last_clock_set_time) + end + device:set_field(LATEST_CLOCK_SET_TIMESTAMP, os.time(), {persist = true}) + if time_diff > 2 then + local preferences = preferencesMap.get_device_parameters(device) + if args.old_st_store.preferences["notificationChild"] ~= device.preferences.notificationChild and args.old_st_store.preferences["notificationChild"] == false and device.preferences.notificationChild == true then + if not device:get_child_by_parent_assigned_key('notification') then + add_child(driver,device,'rgbw-bulb','notification') + end + end + + for id, value in pairs(device.preferences) do + if args.old_st_store.preferences[id] ~= value and preferences and preferences[id] then + local new_parameter_value = preferences_calculate_parameter(preferences_to_numeric_value(device.preferences[id]), preferences[id].size, id) + device:send(Configuration:Set({parameter_number = preferences[id].parameter_number, size = preferences[id].size, configuration_value = new_parameter_value})) + end + end + else + log.info("info_changed running more than once. Cancelling this run. Time diff: " .. time_diff) + end + end +end + +local function switch_set_on_off_handler(value) + return function(driver, device, command) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:send(Basic:Set({ value = value })) + device.thread:call_with_delay(3, function(d) + device:send(SwitchMultilevel:Get({})) + end) + else + device:emit_event(capabilities.switch.switch(value == 0 and "off" or "on")) + local dev = device:get_parent_device() + local config = Configuration:Set({ + parameter_number=get_notification_parameter_number(device), + configuration_value=(value == 0 and 0 or getNotificationValue(device)), + size=4 + }) + local send_configuration = function() + dev:send(config) + end + device.thread:call_with_delay(1,send_configuration) + end + end +end + +local function configuration_report(driver, device, cmd) + if cmd.args.parameter_number == LED_COLOR_CONTROL_PARAMETER_NUMBER and is_gen2(device) then + local hue = valueToHuePercent(cmd.args.configuration_value) + + local ledBarComponent = device.profile.components[LED_BAR_COMPONENT_NAME] + if ledBarComponent ~= nil then + device:emit_component_event(ledBarComponent, capabilities.colorControl.hue(hue)) + device:emit_component_event(ledBarComponent, capabilities.colorControl.saturation(LED_GENERIC_SATURATION)) + end + end +end + +local map_key_attribute_to_capability = { + [CentralScene.key_attributes.KEY_PRESSED_1_TIME] = capabilities.button.button.pushed, + [CentralScene.key_attributes.KEY_RELEASED] = capabilities.button.button.held, + [CentralScene.key_attributes.KEY_HELD_DOWN] = capabilities.button.button.down_hold, + [CentralScene.key_attributes.KEY_PRESSED_2_TIMES] = capabilities.button.button.pushed_2x, + [CentralScene.key_attributes.KEY_PRESSED_3_TIMES] = capabilities.button.button.pushed_3x, + [CentralScene.key_attributes.KEY_PRESSED_4_TIMES] = capabilities.button.button.pushed_4x, + [CentralScene.key_attributes.KEY_PRESSED_5_TIMES] = capabilities.button.button.pushed_5x, +} + +-- Map key attributes to their button value strings for support checking +-- TODO: This mapping and the support check below can likely be removed after a transition period. +-- Once users have interacted with their devices and the supportedButtonValues gets properly +-- set during device initialization, the driver will know which values are supported and +-- won't attempt to emit unsupported events. This code is a temporary safeguard to prevent +-- errors during the transition period. +local map_key_attribute_to_value = { + [CentralScene.key_attributes.KEY_RELEASED] = "held", + [CentralScene.key_attributes.KEY_HELD_DOWN] = "down_hold", +} + +-- TODO: Remove after transition period - button value support checking +-- Helper function to check if a button value is supported. +-- This function can likely be removed after a transition period once devices have +-- their supportedButtonValues properly set. See comment above map_key_attribute_to_value. +local function is_button_value_supported(device, component, value) + if value == nil then + return true -- If no value to check, assume supported + end + + local supported_values_state = device:get_latest_state( + component.id, + capabilities.button.ID, + capabilities.button.supportedButtonValues.NAME + ) + + -- Check multiple possible structures for supportedButtonValues + -- In SmartThings Edge, get_latest_state returns a state object + -- For supportedButtonValues, the array could be in: state.value, or state itself IS the array + local supported_values = nil + if supported_values_state ~= nil then + -- First check .value property (most common structure) + if supported_values_state.value ~= nil then + supported_values = supported_values_state.value + -- Check if state itself is an array (the state IS the array) + -- Check if index 1 exists - if it does and .value doesn't exist, the state itself is the array + elseif type(supported_values_state) == "table" and supported_values_state[1] ~= nil then + supported_values = supported_values_state + end + + -- Check .state.value structure (nested structure) + if supported_values == nil and supported_values_state.state ~= nil and supported_values_state.state.value ~= nil then + supported_values = supported_values_state.state.value + end + end + + if supported_values == nil then + return true -- If no supported values set, assume all are supported (backward compatibility) + end + + -- Check if the value is in the supported values array + if type(supported_values) == "table" then + for _, supported_value in ipairs(supported_values) do + if supported_value == value then + return true + end + end + end + + return false +end + +local function central_scene_notification_handler(self, device, cmd) + if ( cmd.args.scene_number ~= nil and cmd.args.scene_number ~= 0 ) then + local button_number = cmd.args.scene_number + local capability_attribute = map_key_attribute_to_capability[cmd.args.key_attributes] + local additional_fields = { + state_change = true + } + + local event + if capability_attribute ~= nil then + event = capability_attribute(additional_fields) + end + + if event ~= nil then + -- device reports scene notifications from endpoint 0 (main) but central scene events have to be emitted for button components: 1,2,3 + local component_name = button_to_component(button_number) + local comp = device.profile.components[component_name] + if comp ~= nil then + -- TODO: Remove after transition period - button value support checking + -- Check if held or down_hold are supported before emitting. + -- This support check can likely be removed after a transition period once devices + -- have their supportedButtonValues properly set. The driver will then only emit events + -- for values that are actually supported, preventing errors. See comment above map_key_attribute_to_value. + local button_value = map_key_attribute_to_value[cmd.args.key_attributes] + local is_supported = is_button_value_supported(device, comp, button_value) + if button_value == nil or is_supported then + device:emit_component_event(comp, event) + else + -- TODO: Remove after transition period - supportedButtonValues update for old devices + -- Update supportedButtonValues for devices with old values from previous driver versions. + -- After updating, emit the event since the value is now supported. + if supported_button_values[comp.id] ~= nil then + device:emit_component_event( + comp, + capabilities.button.supportedButtonValues( + supported_button_values[comp.id], + { visibility = { displayed = false } } + ) + ) + device:emit_component_event(comp, event) + end + end + end + end + end +end + +------------------------------------------------------------------------------------------- +-- Register message handlers and run driver +------------------------------------------------------------------------------------------- +local inovelli = { + NAME = "Inovelli Z-Wave Switch", + lifecycle_handlers = { + infoChanged = info_changed, + added = device_added, + }, + zwave_handlers = { + [cc.CENTRAL_SCENE] = { + [CentralScene.NOTIFICATION] = central_scene_notification_handler + }, + [cc.CONFIGURATION] = { + [Configuration.REPORT] = configuration_report + } + }, + capability_handlers = { + [capabilities.switch.ID] = { + [capabilities.switch.switch.on.NAME] = switch_set_on_off_handler(SwitchBinary.value.ON_ENABLE), + [capabilities.switch.switch.off.NAME] = switch_set_on_off_handler(SwitchBinary.value.OFF_DISABLE) + }, + [capabilities.colorControl.ID] = { + [capabilities.colorControl.commands.setColor.NAME] = set_color + }, + [capabilities.colorTemperature.ID] = { + [capabilities.colorTemperature.commands.setColorTemperature.NAME] = set_color_temperature + }, + [capabilities.switchLevel.ID] = { + [capabilities.switchLevel.commands.setLevel.NAME] = switch_level_set + }, + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = refresh_handler + } + }, + can_handle = require("inovelli.can_handle"), + sub_drivers = require("inovelli.sub_drivers"), +} + +return inovelli \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/src/inovelli/lzw31-sn/can_handle.lua b/drivers/SmartThings/zwave-switch/src/inovelli/lzw31-sn/can_handle.lua new file mode 100644 index 0000000000..6b70eb3c27 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/inovelli/lzw31-sn/can_handle.lua @@ -0,0 +1,19 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local INOVELLI_MANUFACTURER_ID = 0x031E +local INOVELLI_LZW31SN_PRODUCT_TYPE = 0x0001 +local INOVELLI_DIMMER_PRODUCT_ID = 0x0001 + +local function can_handle_lzw31sn(opts, driver, device, ...) + if device:id_match( + INOVELLI_MANUFACTURER_ID, + INOVELLI_LZW31SN_PRODUCT_TYPE, + INOVELLI_DIMMER_PRODUCT_ID + ) then + return true, require("inovelli.lzw31-sn") + end + return false +end + +return can_handle_lzw31sn diff --git a/drivers/SmartThings/zwave-switch/src/inovelli/lzw31-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli/lzw31-sn/init.lua new file mode 100644 index 0000000000..77a5430681 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/inovelli/lzw31-sn/init.lua @@ -0,0 +1,74 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +--- @type st.zwave.CommandClass.SwitchMultilevel +local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({version=4}) +--- @type st.zwave.CommandClass.Meter +local Meter = (require "st.zwave.CommandClass.Meter")({ version = 3 }) +--- @type st.zwave.CommandClass.Association +local Association = (require "st.zwave.CommandClass.Association")({ version = 1 }) +--- @type st.device +local st_device = require "st.device" + +local supported_button_values = { + ["button1"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button2"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button3"] = {"pushed"} +} + +local LED_BAR_COMPONENT_NAME = "LEDColorConfiguration" + +local function refresh_handler(driver, device) + device:send(SwitchMultilevel:Get({})) + device:send(Meter:Get({ scale = Meter.scale.electric_meter.WATTS })) + device:send(Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS })) +end + +local function device_added(driver, device) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:send(Association:Set({grouping_identifier = 1, node_ids = {driver.environment_info.hub_zwave_id}})) + for _, component in pairs(device.profile.components) do + if component.id ~= "main" and component.id ~= LED_BAR_COMPONENT_NAME then + device:emit_component_event( + component, + capabilities.button.supportedButtonValues( + supported_button_values[component.id], + { visibility = { displayed = false } } + ) + ) + device:emit_component_event( + component, + capabilities.button.numberOfButtons({value = 1}, { visibility = { displayed = false } }) + ) + end + end + refresh_handler(driver, device) + local ledBarComponent = device.profile.components[LED_BAR_COMPONENT_NAME] + if ledBarComponent ~= nil then + device:emit_component_event(ledBarComponent, capabilities.colorControl.hue(1)) + device:emit_component_event(ledBarComponent, capabilities.colorControl.saturation(1)) + end + else + device:emit_event(capabilities.colorControl.hue(1)) + device:emit_event(capabilities.colorControl.saturation(1)) + device:emit_event(capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = 2700, maximum = 6500} })) + device:emit_event(capabilities.switchLevel.level(100)) + device:emit_event(capabilities.switch.switch("off")) + end +end + +local lzw31_sn = { + NAME = "Inovelli LZW31-SN Z-Wave Dimmer", + lifecycle_handlers = { + added = device_added, + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = refresh_handler + } + }, + can_handle = require("inovelli.lzw31-sn.can_handle") +} + +return lzw31_sn \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/src/inovelli/sub_drivers.lua b/drivers/SmartThings/zwave-switch/src/inovelli/sub_drivers.lua new file mode 100644 index 0000000000..e182120ece --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/inovelli/sub_drivers.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load = require "lazy_load_subdriver" + +return { + lazy_load("inovelli.lzw31-sn"), + lazy_load("inovelli.vzw32-sn") +} diff --git a/drivers/SmartThings/zwave-switch/src/inovelli/vzw32-sn/can_handle.lua b/drivers/SmartThings/zwave-switch/src/inovelli/vzw32-sn/can_handle.lua new file mode 100644 index 0000000000..5ed4e272e0 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/inovelli/vzw32-sn/can_handle.lua @@ -0,0 +1,19 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local INOVELLI_MANUFACTURER_ID = 0x031E +local INOVELLI_VZW32_SN_PRODUCT_TYPE = 0x0017 +local INOVELLI_DIMMER_PRODUCT_ID = 0x0001 + +local function can_handle_vzw32_sn(opts, driver, device, ...) + if device:id_match( + INOVELLI_MANUFACTURER_ID, + INOVELLI_VZW32_SN_PRODUCT_TYPE, + INOVELLI_DIMMER_PRODUCT_ID + ) then + return true, require("inovelli.vzw32-sn") + end + return false +end + +return can_handle_vzw32_sn diff --git a/drivers/SmartThings/zwave-switch/src/inovelli/vzw32-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli/vzw32-sn/init.lua new file mode 100644 index 0000000000..0ad1d31430 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/inovelli/vzw32-sn/init.lua @@ -0,0 +1,73 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +--- @type st.zwave.CommandClass.SwitchMultilevel +local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({version=4}) +--- @type st.zwave.CommandClass.SensorMultilevel +local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ version = 7 }) +--- @type st.zwave.CommandClass.Meter +local Meter = (require "st.zwave.CommandClass.Meter")({ version = 3 }) +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +--- @type st.zwave.CommandClass.Association +local Association = (require "st.zwave.CommandClass.Association")({ version = 1 }) +--- @type st.device +local st_device = require "st.device" + +local supported_button_values = { + ["button1"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button2"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button3"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"} +} + +local function refresh_handler(driver, device) + device:send(SwitchMultilevel:Get({})) + device:send(SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.ILLUMINANCE})) + device:send(Meter:Get({ scale = Meter.scale.electric_meter.WATTS })) + device:send(Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS })) + device:send(Notification:Get({notification_type = Notification.notification_type.HOME_SECURITY, event = Notification.event.home_security.MOTION_DETECTION})) +end + +local function device_added(driver, device) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:send(Association:Set({grouping_identifier = 1, node_ids = {driver.environment_info.hub_zwave_id}})) + for _, component in pairs(device.profile.components) do + if component.id ~= "main" and component.id ~= "LEDColorConfiguration" then + device:emit_component_event( + component, + capabilities.button.supportedButtonValues( + supported_button_values[component.id], + { visibility = { displayed = false } } + ) + ) + device:emit_component_event( + component, + capabilities.button.numberOfButtons({value = 1}, { visibility = { displayed = false } }) + ) + end + end + refresh_handler(driver, device) + else + device:emit_event(capabilities.colorControl.hue(1)) + device:emit_event(capabilities.colorControl.saturation(1)) + device:emit_event(capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = 2700, maximum = 6500} })) + device:emit_event(capabilities.switchLevel.level(100)) + device:emit_event(capabilities.switch.switch("off")) + end +end + +local vzw32_sn = { + NAME = "Inovelli VZW32-SN mmWave Dimmer", + lifecycle_handlers = { + added = device_added, + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = refresh_handler + } + }, + can_handle = require("inovelli.vzw32-sn.can_handle") +} + +return vzw32_sn \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-switch/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..45115081e4 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/lazy_load_subdriver.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +return function(sub_driver_name) + -- gets the current lua libs api version + local ZwaveDriver = require "st.zwave.driver" + local version = require "version" + + if version.api >= 16 then + return ZwaveDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZwaveDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end + +end diff --git a/drivers/SmartThings/zwave-switch/src/multi-metering-switch/can_handle.lua b/drivers/SmartThings/zwave-switch/src/multi-metering-switch/can_handle.lua new file mode 100644 index 0000000000..89fd6ef17f --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/multi-metering-switch/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local function can_handle_multi_metering_switch(opts, driver, device, ...) + local fingerprints = require("multi-metering-switch.fingerprints") + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("multi-metering-switch") + return true, subdriver + end + end + return false +end + +return can_handle_multi_metering_switch diff --git a/drivers/SmartThings/zwave-switch/src/multi-metering-switch/fingerprints.lua b/drivers/SmartThings/zwave-switch/src/multi-metering-switch/fingerprints.lua new file mode 100644 index 0000000000..d35e06a5f0 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/multi-metering-switch/fingerprints.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + {mfr = 0x0086, prod = 0x0003, model = 0x0084}, -- Aeotec Nano Switch 1 + {mfr = 0x0086, prod = 0x0103, model = 0x0084}, -- Aeotec Nano Switch 1 + {mfr = 0x0086, prod = 0x0203, model = 0x0084}, -- AU Aeotec Nano Switch 1 + {mfr = 0x027A, prod = 0xA000, model = 0xA004}, -- Zooz ZEN Power Strip 1 + {mfr = 0x015F, prod = 0x3102, model = 0x0201}, -- WYFY Touch 1-button Switch + {mfr = 0x015F, prod = 0x3102, model = 0x0202}, -- WYFY Touch 2-button Switch + {mfr = 0x015F, prod = 0x3102, model = 0x0204}, -- WYFY Touch 4-button Switch + {mfr = 0x015F, prod = 0x3111, model = 0x5102}, -- WYFY Touch 1-button Switch + {mfr = 0x015F, prod = 0x3121, model = 0x5102}, -- WYFY Touch 2-button Switch + {mfr = 0x015F, prod = 0x3141, model = 0x5102}, -- WYFY Touch 4-button Switch + {mfr = 0x0460, prod = 0x0002, model = 0x0081}, -- Shelly Wave 2PM + {mfr = 0x0460, prod = 0x0002, model = 0x008C}, -- Shelly Wave Pro 2 + {mfr = 0x0460, prod = 0x0002, model = 0x008D}, -- Shelly Wave Pro 2PM +} diff --git a/drivers/SmartThings/zwave-switch/src/multi-metering-switch/init.lua b/drivers/SmartThings/zwave-switch/src/multi-metering-switch/init.lua index efe7d35893..ca4c8951d9 100644 --- a/drivers/SmartThings/zwave-switch/src/multi-metering-switch/init.lua +++ b/drivers/SmartThings/zwave-switch/src/multi-metering-switch/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local st_device = require "st.device" local utils = require "st.utils" @@ -31,32 +20,6 @@ local MULTI_METERING_SWITCH_CONFIGURATION_MAP = require "multi-metering-switch/m local PARENT_ENDPOINT = 1 -local MULTI_METERING_SWITCH_FINGERPRINTS = { - {mfr = 0x0086, prod = 0x0003, model = 0x0084}, -- Aeotec Nano Switch 1 - {mfr = 0x0086, prod = 0x0103, model = 0x0084}, -- Aeotec Nano Switch 1 - {mfr = 0x0086, prod = 0x0203, model = 0x0084}, -- AU Aeotec Nano Switch 1 - {mfr = 0x027A, prod = 0xA000, model = 0xA004}, -- Zooz ZEN Power Strip 1 - {mfr = 0x015F, prod = 0x3102, model = 0x0201}, -- WYFY Touch 1-button Switch - {mfr = 0x015F, prod = 0x3102, model = 0x0202}, -- WYFY Touch 2-button Switch - {mfr = 0x015F, prod = 0x3102, model = 0x0204}, -- WYFY Touch 4-button Switch - {mfr = 0x015F, prod = 0x3111, model = 0x5102}, -- WYFY Touch 1-button Switch - {mfr = 0x015F, prod = 0x3121, model = 0x5102}, -- WYFY Touch 2-button Switch - {mfr = 0x015F, prod = 0x3141, model = 0x5102}, -- WYFY Touch 4-button Switch - {mfr = 0x0460, prod = 0x0002, model = 0x0081}, -- Shelly Wave 2PM - {mfr = 0x0460, prod = 0x0002, model = 0x008C}, -- Shelly Wave Pro 2 - {mfr = 0x0460, prod = 0x0002, model = 0x008D}, -- Shelly Wave Pro 2PM -} - -local function can_handle_multi_metering_switch(opts, driver, device, ...) - for _, fingerprint in ipairs(MULTI_METERING_SWITCH_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("multi-metering-switch") - return true, subdriver - end - end - return false -end - local function find_child(parent, ep_id) if ep_id == PARENT_ENDPOINT then return parent @@ -199,7 +162,7 @@ local multi_metering_switch = { init = device_init, added = device_added }, - can_handle = can_handle_multi_metering_switch, + can_handle = require("multi-metering-switch.can_handle"), } return multi_metering_switch diff --git a/drivers/SmartThings/zwave-switch/src/multi-metering-switch/multi_metering_switch_configurations.lua b/drivers/SmartThings/zwave-switch/src/multi-metering-switch/multi_metering_switch_configurations.lua index dfb773efc1..af047646d5 100644 --- a/drivers/SmartThings/zwave-switch/src/multi-metering-switch/multi_metering_switch_configurations.lua +++ b/drivers/SmartThings/zwave-switch/src/multi-metering-switch/multi_metering_switch_configurations.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local devices = { AEOTEC_NANO_SWITCH_1 = { diff --git a/drivers/SmartThings/zwave-switch/src/multichannel-device/can_handle.lua b/drivers/SmartThings/zwave-switch/src/multichannel-device/can_handle.lua new file mode 100644 index 0000000000..49464a3240 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/multichannel-device/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" + +local function can_handle_multichannel_device(opts, driver, device, ...) + if device:supports_capability(capabilities.zwMultichannel) then + local subdriver = require("multichannel-device") + return true, subdriver + end + return false +end + +return can_handle_multichannel_device diff --git a/drivers/SmartThings/zwave-switch/src/multichannel-device/init.lua b/drivers/SmartThings/zwave-switch/src/multichannel-device/init.lua index 54349673e8..82cba1330f 100644 --- a/drivers/SmartThings/zwave-switch/src/multichannel-device/init.lua +++ b/drivers/SmartThings/zwave-switch/src/multichannel-device/init.lua @@ -1,18 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local cc = require "st.zwave.CommandClass" -local capabilities = require "st.capabilities" local st_device = require "st.device" local MultiChannel = (require "st.zwave.CommandClass.MultiChannel")({ version = 3 }) local utils = require "st.utils" @@ -27,14 +15,6 @@ local map_device_class_to_profile = { [0xA1] = "generic-sensor" } -local function can_handle_multichannel_device(opts, driver, device, ...) - if device:supports_capability(capabilities.zwMultichannel) then - local subdriver = require("multichannel-device") - return true, subdriver - end - return false -end - local function find_child(device, src_channel) if src_channel == 0 then return device @@ -91,7 +71,7 @@ local multichannel_device = { [MultiChannel.CAPABILITY_REPORT] = capability_get_report_handler } }, - can_handle = can_handle_multichannel_device + can_handle = require("multichannel-device.can_handle") } -return multichannel_device \ No newline at end of file +return multichannel_device diff --git a/drivers/SmartThings/zwave-switch/src/preferences.lua b/drivers/SmartThings/zwave-switch/src/preferences.lua index 73ed5e52f3..824e570070 100644 --- a/drivers/SmartThings/zwave-switch/src/preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/preferences.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local AEOTEC_HEAVY_DUTY_SWITCH = { PARAMETERS = { @@ -70,6 +59,44 @@ local devices = { switchType = {parameter_number = 22, size = 1} } }, + INOVELLI_VZW32_SN = { + MATCHING_MATRIX = { + mfrs = 0x031E, + product_types = {0x0017}, + product_ids = 0x0001 + }, + PARAMETERS = { + parameter158 = {parameter_number = 158, size = 1}, + parameter52 = {parameter_number = 52, size = 1}, + parameter1 = {parameter_number = 1, size = 1}, + parameter2 = {parameter_number = 2, size = 1}, + parameter3 = {parameter_number = 3, size = 1}, + parameter4 = {parameter_number = 4, size = 1}, + parameter9 = {parameter_number = 9, size = 1}, + parameter10 = {parameter_number = 10, size = 1}, + parameter15 = {parameter_number = 15, size = 1}, + parameter18 = {parameter_number = 18, size = 1}, + parameter19 = {parameter_number = 19, size = 2}, + parameter20 = {parameter_number = 20, size = 2}, + parameter50 = {parameter_number = 50, size = 1}, + parameter95 = {parameter_number = 95, size = 1}, + parameter96 = {parameter_number = 96, size = 1}, + parameter97 = {parameter_number = 97, size = 1}, + parameter98 = {parameter_number = 98, size = 1}, + parameter101 = {parameter_number = 101, size = 2}, + parameter102 = {parameter_number = 102, size = 2}, + parameter103 = {parameter_number = 103, size = 2}, + parameter104 = {parameter_number = 104, size = 2}, + parameter105 = {parameter_number = 105, size = 2}, + parameter106 = {parameter_number = 106, size = 2}, + parameter108 = {parameter_number = 108, size = 4}, + parameter110 = {parameter_number = 110, size = 2}, + parameter111 = {parameter_number = 111, size = 1}, + parameter112 = {parameter_number = 112, size = 1}, + parameter113 = {parameter_number = 113, size = 1}, + parameter114 = {parameter_number = 114, size = 4} + } + }, QUBINO_FLUSH_DIMMER = { MATCHING_MATRIX = { mfrs = 0x0159, @@ -428,4 +455,4 @@ preferences.to_numeric_value = function(new_value) return numeric end -return preferences \ No newline at end of file +return preferences diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/can_handle.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/can_handle.lua new file mode 100644 index 0000000000..3312c13838 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local constants = require "qubino-switches.constants.qubino-constants" + +local function can_handle_qubino_flush_relay(opts, driver, device, cmd, ...) + if device:id_match(constants.QUBINO_MFR) then + local subdriver = require("qubino-switches") + return true, subdriver + end + return false +end + +return can_handle_qubino_flush_relay diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/constants/qubino-constants.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/constants/qubino-constants.lua index 31613f40b9..8395eb78fe 100644 --- a/drivers/SmartThings/zwave-switch/src/qubino-switches/constants/qubino-constants.lua +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/constants/qubino-constants.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 return { TEMP_SENSOR_WORK_THRESHOLD = -20, -- Celsius degrees diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/fingerprints.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/fingerprints.lua new file mode 100644 index 0000000000..056b743e8f --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/fingerprints.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + {mfr = 0x0159, prod = 0x0001, model = 0x0051, deviceProfile = "qubino-flush-dimmer"}, -- Qubino Flush Dimmer + {mfr = 0x0159, prod = 0x0001, model = 0x0052, deviceProfile = "qubino-din-dimmer"}, -- Qubino DIN Dimmer + {mfr = 0x0159, prod = 0x0001, model = 0x0053, deviceProfile = "qubino-flush-dimmer-0-10V"}, -- Qubino Flush Dimmer 0-10V + {mfr = 0x0159, prod = 0x0001, model = 0x0055, deviceProfile = "qubino-mini-dimmer"}, -- Qubino Mini Dimmer + {mfr = 0x0159, prod = 0x0002, model = 0x0051, deviceProfile = "qubino-flush2-relay"}, -- Qubino Flush 2 Relay + {mfr = 0x0159, prod = 0x0002, model = 0x0052, deviceProfile = "qubino-flush1-relay"}, -- Qubino Flush 1 Relay + {mfr = 0x0159, prod = 0x0002, model = 0x0053, deviceProfile = "qubino-flush1d-relay"} -- Qubino Flush 1D Relay +} diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/init.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/init.lua index d883aa4821..15d541366a 100644 --- a/drivers/SmartThings/zwave-switch/src/qubino-switches/init.lua +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -26,19 +15,11 @@ local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({version=1}) local constants = require "qubino-switches/constants/qubino-constants" -local QUBINO_FINGERPRINTS = { - {mfr = 0x0159, prod = 0x0001, model = 0x0051, deviceProfile = "qubino-flush-dimmer"}, -- Qubino Flush Dimmer - {mfr = 0x0159, prod = 0x0001, model = 0x0052, deviceProfile = "qubino-din-dimmer"}, -- Qubino DIN Dimmer - {mfr = 0x0159, prod = 0x0001, model = 0x0053, deviceProfile = "qubino-flush-dimmer-0-10V"}, -- Qubino Flush Dimmer 0-10V - {mfr = 0x0159, prod = 0x0001, model = 0x0055, deviceProfile = "qubino-mini-dimmer"}, -- Qubino Mini Dimmer - {mfr = 0x0159, prod = 0x0002, model = 0x0051, deviceProfile = "qubino-flush2-relay"}, -- Qubino Flush 2 Relay - {mfr = 0x0159, prod = 0x0002, model = 0x0052, deviceProfile = "qubino-flush1-relay"}, -- Qubino Flush 1 Relay - {mfr = 0x0159, prod = 0x0002, model = 0x0053, deviceProfile = "qubino-flush1d-relay"} -- Qubino Flush 1D Relay -} +local fingerprints = require("qubino-switches.fingerprints") local function getDeviceProfile(device, isTemperatureSensorOnboard) local newDeviceProfile - for _, fingerprint in ipairs(QUBINO_FINGERPRINTS) do + for _, fingerprint in ipairs(fingerprints) do if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then newDeviceProfile = fingerprint.deviceProfile if(isTemperatureSensorOnboard) then @@ -51,14 +32,6 @@ local function getDeviceProfile(device, isTemperatureSensorOnboard) return nil end -local function can_handle_qubino_flush_relay(opts, driver, device, cmd, ...) - if device:id_match(constants.QUBINO_MFR) then - local subdriver = require("qubino-switches") - return true, subdriver - end - return false -end - local function add_temperature_sensor_if_needed(device) if not (device:supports_capability_by_id(capabilities.temperatureMeasurement.ID)) then local new_profile = getDeviceProfile(device, true) @@ -112,7 +85,7 @@ end local qubino_relays = { NAME = "Qubino Relays", - can_handle = can_handle_qubino_flush_relay, + can_handle = require("qubino-switches.can_handle"), zwave_handlers = { [cc.SENSOR_MULTILEVEL] = { [SensorMultilevel.REPORT] = sensor_multilevel_report @@ -126,10 +99,7 @@ local qubino_relays = { lifecycle_handlers = { added = device_added }, - sub_drivers = { - require("qubino-switches/qubino-relays"), - require("qubino-switches/qubino-dimmer") - } + sub_drivers = require("qubino-switches.sub_drivers"), } return qubino_relays diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/can_handle.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/can_handle.lua new file mode 100644 index 0000000000..9588fdccae --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local function can_handle_qubino_dimmer(opts, driver, device, ...) + local fingerprints = require("qubino-switches.qubino-dimmer.fingerprints") + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("qubino-switches.qubino-dimmer") + end + end + return false +end + +return can_handle_qubino_dimmer diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/fingerprints.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/fingerprints.lua new file mode 100644 index 0000000000..32946fe973 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + {mfr = 0x0159, prod = 0x0001, model = 0x0051}, -- Qubino Flush Dimmer + {mfr = 0x0159, prod = 0x0001, model = 0x0052}, -- Qubino DIN Dimmer + {mfr = 0x0159, prod = 0x0001, model = 0x0053}, -- Qubino Flush Dimmer 0-10V + {mfr = 0x0159, prod = 0x0001, model = 0x0055} -- Qubino Mini Dimmer +} diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/init.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/init.lua index 03023adffa..d0f0e607a5 100644 --- a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/init.lua +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/init.lua @@ -1,35 +1,8 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local MultichannelAssociation = (require "st.zwave.CommandClass.MultiChannelAssociation")({ version = 3 }) -local QUBINO_DIMMER_FINGERPRINTS = { - {mfr = 0x0159, prod = 0x0001, model = 0x0051}, -- Qubino Flush Dimmer - {mfr = 0x0159, prod = 0x0001, model = 0x0052}, -- Qubino DIN Dimmer - {mfr = 0x0159, prod = 0x0001, model = 0x0053}, -- Qubino Flush Dimmer 0-10V - {mfr = 0x0159, prod = 0x0001, model = 0x0055} -- Qubino Mini Dimmer -} - -local function can_handle_qubino_dimmer(opts, driver, device, ...) - for _, fingerprint in ipairs(QUBINO_DIMMER_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end - local function do_configure(self, device) device:send(MultichannelAssociation:Remove({grouping_identifier = 1, node_ids = {}})) device:send(MultichannelAssociation:Set({grouping_identifier = 1, node_ids = {self.environment_info.hub_zwave_id}})) @@ -40,10 +13,8 @@ local qubino_dimmer = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_qubino_dimmer, - sub_drivers = { - require("qubino-switches/qubino-dimmer/qubino-din-dimmer") - } + can_handle = require("qubino-switches.qubino-dimmer.can_handle"), + sub_drivers = require("qubino-switches.qubino-dimmer.sub_drivers"), } return qubino_dimmer diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/qubino-din-dimmer/can_handle.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/qubino-din-dimmer/can_handle.lua new file mode 100644 index 0000000000..b0da891a74 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/qubino-din-dimmer/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_qubino_din_dimmer(opts, driver, device, ...) + -- Qubino Din Dimmer: mfr = 0x0159, prod = 0x0001, model = 0x0052 + if device:id_match(0x0159, 0x0001, 0x0052) then + return true, require("qubino-switches.qubino-dimmer.qubino-din-dimmer") + end + return false +end + +return can_handle_qubino_din_dimmer diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/qubino-din-dimmer/init.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/qubino-din-dimmer/init.lua index f763c36c34..e47d4b149f 100644 --- a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/qubino-din-dimmer/init.lua +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/qubino-din-dimmer/init.lua @@ -1,28 +1,9 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) local MultichannelAssociation = (require "st.zwave.CommandClass.MultiChannelAssociation")({ version = 3 }) -local function can_handle_qubino_din_dimmer(opts, driver, device, ...) - -- Qubino Din Dimmer: mfr = 0x0159, prod = 0x0001, model = 0x0052 - if device:id_match(0x0159, 0x0001, 0x0052) then - return true - end - return false -end - local function do_configure(self, device) device:send(MultichannelAssociation:Remove({grouping_identifier = 1, node_ids = {}})) device:send(MultichannelAssociation:Set({grouping_identifier = 1, node_ids = {self.environment_info.hub_zwave_id}})) @@ -34,7 +15,7 @@ local qubino_din_dimmer = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_qubino_din_dimmer + can_handle = require("qubino-switches.qubino-dimmer.qubino-din-dimmer.can_handle") } return qubino_din_dimmer diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/sub_drivers.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/sub_drivers.lua new file mode 100644 index 0000000000..96da43c3e6 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-dimmer/sub_drivers.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load = require "lazy_load_subdriver" + +return { + lazy_load("qubino-switches.qubino-dimmer.qubino-din-dimmer"), +} diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/can_handle.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/can_handle.lua new file mode 100644 index 0000000000..4eb7c04595 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/can_handle.lua @@ -0,0 +1,19 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local function can_handle_qubino_flush_relay(opts, driver, device, cmd, ...) + local fingerprints = { + {mfr = 0x0159, prod = 0x0002, model = 0x0051}, -- Qubino Flush 2 Relay + {mfr = 0x0159, prod = 0x0002, model = 0x0052}, -- Qubino Flush 1 Relay + {mfr = 0x0159, prod = 0x0002, model = 0x0053} -- Qubino Flush 1D Relay + } + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("qubino-switches.qubino-relays") + end + end + return false +end + +return can_handle_qubino_flush_relay diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/init.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/init.lua index b6e3918cfb..8199235310 100644 --- a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/init.lua +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/init.lua @@ -1,34 +1,8 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local Association = (require "st.zwave.CommandClass.Association")({ version = 2 }) -local QUBINO_FLUSH_RELAY_FINGERPRINT = { - {mfr = 0x0159, prod = 0x0002, model = 0x0051}, -- Qubino Flush 2 Relay - {mfr = 0x0159, prod = 0x0002, model = 0x0052}, -- Qubino Flush 1 Relay - {mfr = 0x0159, prod = 0x0002, model = 0x0053} -- Qubino Flush 1D Relay -} - -local function can_handle_qubino_flush_relay(opts, driver, device, cmd, ...) - for _, fingerprint in ipairs(QUBINO_FLUSH_RELAY_FINGERPRINT) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end - local function do_configure(self, device) local association_cmd = Association:Set({grouping_identifier = 2, node_ids = {self.environment_info.hub_zwave_id}}) -- This command needs to be sent before creating component @@ -40,12 +14,8 @@ end local qubino_relays = { NAME = "Qubino Relays", - can_handle = can_handle_qubino_flush_relay, - sub_drivers = { - require("qubino-switches/qubino-relays/qubino-flush-2-relay"), - require("qubino-switches/qubino-relays/qubino-flush-1-relay"), - require("qubino-switches/qubino-relays/qubino-flush-1d-relay") - }, + can_handle = require("qubino-switches.qubino-relays.can_handle"), + sub_drivers = require("qubino-switches.qubino-relays.sub_drivers"), lifecycle_handlers = { doConfigure = do_configure }, diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1-relay/can_handle.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1-relay/can_handle.lua new file mode 100644 index 0000000000..f91589bce9 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1-relay/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local QUBINO_FLUSH_1_RELAY_FINGERPRINT = {mfr = 0x0159, prod = 0x0002, model = 0x0052} + +local function can_handle_qubino_flush_1_relay(opts, driver, device, ...) + if device:id_match(QUBINO_FLUSH_1_RELAY_FINGERPRINT.mfr, QUBINO_FLUSH_1_RELAY_FINGERPRINT.prod, QUBINO_FLUSH_1_RELAY_FINGERPRINT.model) then + return true, require("qubino-switches.qubino-relays.qubino-flush-1-relay") + end + return false +end + +return can_handle_qubino_flush_1_relay diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1-relay/init.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1-relay/init.lua index 7fef39539b..61cf89394e 100644 --- a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1-relay/init.lua +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1-relay/init.lua @@ -1,28 +1,11 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 --- @type st.zwave.CommandClass.Association local Association = (require "st.zwave.CommandClass.Association")({version=2}) --- @type st.zwave.CommandClass.MultiChannelAssociation local MultiChannelAssociation = (require "st.zwave.CommandClass.MultiChannelAssociation")({version=3}) -local QUBINO_FLUSH_1_RELAY_FINGERPRINT = {mfr = 0x0159, prod = 0x0002, model = 0x0052} - -local function can_handle_qubino_flush_1_relay(opts, driver, device, ...) - return device:id_match(QUBINO_FLUSH_1_RELAY_FINGERPRINT.mfr, QUBINO_FLUSH_1_RELAY_FINGERPRINT.prod, QUBINO_FLUSH_1_RELAY_FINGERPRINT.model) -end - local function do_configure(self, device) -- Hub automatically adds device to multiChannelAssosciationGroup and this needs to be removed device:send(MultiChannelAssociation:Remove({grouping_identifier = 1, node_ids = {}})) @@ -40,7 +23,7 @@ local qubino_flush_1_relay = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_qubino_flush_1_relay + can_handle = require("qubino-switches.qubino-relays.qubino-flush-1-relay.can_handle") } return qubino_flush_1_relay diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1d-relay/can_handle.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1d-relay/can_handle.lua new file mode 100644 index 0000000000..9d5f69bb34 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1d-relay/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local QUBINO_FLUSH_1D_RELAY_FINGERPRINT = {mfr = 0x0159, prod = 0x0002, model = 0x0053} + +local function can_handle_qubino_flush_1d_relay(opts, driver, device, ...) + if device:id_match(QUBINO_FLUSH_1D_RELAY_FINGERPRINT.mfr, QUBINO_FLUSH_1D_RELAY_FINGERPRINT.prod, QUBINO_FLUSH_1D_RELAY_FINGERPRINT.model) then + return true, require("qubino-switches.qubino-relays.qubino-flush-1d-relay") + end + return false +end + +return can_handle_qubino_flush_1d_relay diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1d-relay/init.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1d-relay/init.lua index 55b900920c..8803a85af2 100644 --- a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1d-relay/init.lua +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-1d-relay/init.lua @@ -1,25 +1,8 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local MultichannelAssociation = (require "st.zwave.CommandClass.MultiChannelAssociation")({ version = 3 }) -local QUBINO_FLUSH_1D_RELAY_FINGERPRINT = {mfr = 0x0159, prod = 0x0002, model = 0x0053} - -local function can_handle_qubino_flush_1d_relay(opts, driver, device, ...) - return device:id_match(QUBINO_FLUSH_1D_RELAY_FINGERPRINT.mfr, QUBINO_FLUSH_1D_RELAY_FINGERPRINT.prod, QUBINO_FLUSH_1D_RELAY_FINGERPRINT.model) -end - local function do_configure(self, device) device:send(MultichannelAssociation:Set({grouping_identifier = 2, node_ids = {self.environment_info.hub_zwave_id}})) device:refresh() @@ -30,7 +13,7 @@ local qubino_flush_1d_relay = { lifecycle_handlers = { doConfigure = do_configure }, - can_handle = can_handle_qubino_flush_1d_relay + can_handle = require("qubino-switches.qubino-relays.qubino-flush-1d-relay.can_handle") } return qubino_flush_1d_relay diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-2-relay/can_handle.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-2-relay/can_handle.lua new file mode 100644 index 0000000000..f3cb5f83e9 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-2-relay/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local QUBINO_FLUSH_2_RELAY_FINGERPRINT = { mfr = 0x0159, prod = 0x0002, model = 0x0051 } + +local function can_handle_qubino_flush_2_relay(opts, driver, device, ...) + if device:id_match(QUBINO_FLUSH_2_RELAY_FINGERPRINT.mfr, QUBINO_FLUSH_2_RELAY_FINGERPRINT.prod, QUBINO_FLUSH_2_RELAY_FINGERPRINT.model) then + return true, require("qubino-switches.qubino-relays.qubino-flush-2-relay") + end + return false +end + +return can_handle_qubino_flush_2_relay diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-2-relay/init.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-2-relay/init.lua index c0a8d0c967..8f60cc1e58 100644 --- a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-2-relay/init.lua +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/qubino-flush-2-relay/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local st_device = require "st.device" local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -30,12 +19,6 @@ local utils = require "st.utils" local CHILD_SWITCH_EP = 2 local CHILD_TEMP_SENSOR_EP = 3 -local QUBINO_FLUSH_2_RELAY_FINGERPRINT = { mfr = 0x0159, prod = 0x0002, model = 0x0051 } - -local function can_handle_qubino_flush_2_relay(opts, driver, device, ...) - return device:id_match(QUBINO_FLUSH_2_RELAY_FINGERPRINT.mfr, QUBINO_FLUSH_2_RELAY_FINGERPRINT.prod, QUBINO_FLUSH_2_RELAY_FINGERPRINT.model) -end - local function component_to_endpoint(device, component_id) return { 1 } end @@ -178,7 +161,7 @@ local qubino_flush_2_relay = { [capabilities.refresh.commands.refresh.NAME] = do_refresh } }, - can_handle = can_handle_qubino_flush_2_relay + can_handle = require("qubino-switches.qubino-relays.qubino-flush-2-relay.can_handle") } return qubino_flush_2_relay diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/sub_drivers.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/sub_drivers.lua new file mode 100644 index 0000000000..27319d943b --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/qubino-relays/sub_drivers.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load = require "lazy_load_subdriver" +return { + lazy_load("qubino-switches.qubino-relays.qubino-flush-1-relay"), + lazy_load("qubino-switches.qubino-relays.qubino-flush-1d-relay"), + lazy_load("qubino-switches.qubino-relays.qubino-flush-2-relay"), +} diff --git a/drivers/SmartThings/zwave-switch/src/qubino-switches/sub_drivers.lua b/drivers/SmartThings/zwave-switch/src/qubino-switches/sub_drivers.lua new file mode 100644 index 0000000000..09a60bf335 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/qubino-switches/sub_drivers.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load = require "lazy_load_subdriver" + +return { + lazy_load("qubino-switches.qubino-relays"), + lazy_load("qubino-switches.qubino-dimmer"), +} diff --git a/drivers/SmartThings/zwave-switch/src/switch_utils.lua b/drivers/SmartThings/zwave-switch/src/switch_utils.lua index 2d0d2f4d89..66ad4715f9 100644 --- a/drivers/SmartThings/zwave-switch/src/switch_utils.lua +++ b/drivers/SmartThings/zwave-switch/src/switch_utils.lua @@ -1,16 +1,5 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local switch_utils = {} @@ -20,4 +9,4 @@ switch_utils.emit_event_if_latest_state_missing = function(device, component, ca end end -return switch_utils \ No newline at end of file +return switch_utils diff --git a/drivers/SmartThings/zwave-switch/src/test/test_aeon_smart_strip.lua b/drivers/SmartThings/zwave-switch/src/test/test_aeon_smart_strip.lua index 0985633cdd..f11a379666 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_aeon_smart_strip.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_aeon_smart_strip.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_dimmer_switch.lua b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_dimmer_switch.lua index 07e64cffa2..6fb6c9c5ef 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_dimmer_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_dimmer_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_dual_nano_switch_configuration.lua b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_dual_nano_switch_configuration.lua index c5c3331ba6..272321f53f 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_dual_nano_switch_configuration.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_dual_nano_switch_configuration.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_heavy_duty_switch.lua b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_heavy_duty_switch.lua index 857411c120..a11fe3abcb 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_heavy_duty_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_heavy_duty_switch.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" @@ -479,4 +468,4 @@ test.register_coroutine_test( end ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_metering_switch_configuration.lua b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_metering_switch_configuration.lua index d0d87cce8e..2ebeb664bb 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_metering_switch_configuration.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_metering_switch_configuration.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_nano_dimmer.lua b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_nano_dimmer.lua index b10fdfe4c9..db1e4c2498 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_nano_dimmer.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_nano_dimmer.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_nano_dimmer_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_nano_dimmer_preferences.lua index dba043703f..a8206a0052 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_nano_dimmer_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_nano_dimmer_preferences.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch.lua b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch.lua index d5878b6fbc..fdba32d9cb 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch_7_eu.lua b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch_7_eu.lua index 50c1a98a57..079616dba4 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch_7_eu.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch_7_eu.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch_7_us.lua b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch_7_us.lua index 6e65c5ffab..0ecb71e59d 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch_7_us.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch_7_us.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch_gen5.lua b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch_gen5.lua index a1e3607ce3..4c2b76fdf6 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch_gen5.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_aeotec_smart_switch_gen5.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_dawon_smart_plug.lua b/drivers/SmartThings/zwave-switch/src/test/test_dawon_smart_plug.lua index 543d3b0b97..cbfe5f42a0 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_dawon_smart_plug.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_dawon_smart_plug.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_dawon_wall_smart_switch.lua b/drivers/SmartThings/zwave-switch/src/test/test_dawon_wall_smart_switch.lua index bfa8073280..ee48f99494 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_dawon_wall_smart_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_dawon_wall_smart_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_eaton_5_scene_keypad.lua b/drivers/SmartThings/zwave-switch/src/test/test_eaton_5_scene_keypad.lua index 5157669f72..d50b7cdaa5 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_eaton_5_scene_keypad.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_eaton_5_scene_keypad.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_eaton_accessory_dimmer.lua b/drivers/SmartThings/zwave-switch/src/test/test_eaton_accessory_dimmer.lua index 2ff605d146..8b5e96b161 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_eaton_accessory_dimmer.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_eaton_accessory_dimmer.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_eaton_anyplace_switch.lua b/drivers/SmartThings/zwave-switch/src/test/test_eaton_anyplace_switch.lua index 1661c34f2c..6815a408da 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_eaton_anyplace_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_eaton_anyplace_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_eaton_rf_dimmer.lua b/drivers/SmartThings/zwave-switch/src/test/test_eaton_rf_dimmer.lua index d939e78d12..99c6ae4068 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_eaton_rf_dimmer.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_eaton_rf_dimmer.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_ecolink_switch.lua b/drivers/SmartThings/zwave-switch/src/test/test_ecolink_switch.lua index c208be9849..4f5557f9a3 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_ecolink_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_ecolink_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_double_switch.lua b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_double_switch.lua index 86b830be19..59934e6439 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_double_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_double_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_single_switch.lua b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_single_switch.lua index cafd5ee79b..0b40d6be33 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_single_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_single_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_wall_plug_eu.lua b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_wall_plug_eu.lua index 94ccbdb384..58ef03fc3f 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_wall_plug_eu.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_wall_plug_eu.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_wall_plug_uk_configuration.lua b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_wall_plug_uk_configuration.lua index 65636f6c22..44aae0f2fa 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_wall_plug_uk_configuration.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_wall_plug_uk_configuration.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_wall_plug_us.lua b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_wall_plug_us.lua index f0ed1f2520..8aa815e829 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_wall_plug_us.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_wall_plug_us.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_walli_dimmer_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_walli_dimmer_preferences.lua index fc42034137..db69dc9845 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_walli_dimmer_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_walli_dimmer_preferences.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_walli_double_switch.lua b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_walli_double_switch.lua index 42144a996a..f4e41b5bee 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_walli_double_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_walli_double_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_walli_double_switch_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_walli_double_switch_preferences.lua index 3dbdd66622..2771cbc3a7 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_fibaro_walli_double_switch_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_fibaro_walli_double_switch_preferences.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_generic_zwave_device1.lua b/drivers/SmartThings/zwave-switch/src/test/test_generic_zwave_device1.lua index 89056f13e9..309d2c79b0 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_generic_zwave_device1.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_generic_zwave_device1.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_go_control_plug_in_switch_configuraton.lua b/drivers/SmartThings/zwave-switch/src/test/test_go_control_plug_in_switch_configuraton.lua index abbce9082b..9b72af072e 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_go_control_plug_in_switch_configuraton.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_go_control_plug_in_switch_configuraton.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_honeywell_dimmer.lua b/drivers/SmartThings/zwave-switch/src/test/test_honeywell_dimmer.lua index cde284936a..4125fc6f00 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_honeywell_dimmer.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_honeywell_dimmer.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_2_channel_smart_plug.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_2_channel_smart_plug.lua index 12753b2879..bc752ec6f1 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_2_channel_smart_plug.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_2_channel_smart_plug.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_button.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_button.lua index 59fa304000..0863f9eb43 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_button.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_button.lua @@ -1,23 +1,14 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" local zw = require "st.zwave" local zw_test_utils = require "integration_test.zwave_test_utils" -local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) local CentralScene = (require "st.zwave.CommandClass.CentralScene")({version=1}) +local Association = (require "st.zwave.CommandClass.Association")({ version = 1 }) +local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({version=4}) +local Meter = (require "st.zwave.CommandClass.Meter")({ version = 3 }) local t_utils = require "integration_test.utils" local INOVELLI_MANUFACTURER_ID = 0x031E @@ -66,8 +57,8 @@ end test.set_test_init_function(test_init) local supported_button_values = { - ["button1"] = {"pushed", "pushed_2x", "pushed_3x", "pushed_4x", "pushed_5x"}, - ["button2"] = {"pushed", "pushed_2x", "pushed_3x", "pushed_4x", "pushed_5x"}, + ["button1"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button2"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, ["button3"] = {"pushed"} } @@ -100,9 +91,44 @@ test.register_coroutine_test( test.socket.zwave:__expect_send( zw_test_utils.zwave_test_build_send_command( mock_inovelli_dimmer, - Basic:Get({}) + Association:Set({ grouping_identifier = 1, node_ids = {}, payload = "\x01" }) ) ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_dimmer, + SwitchMultilevel:Get({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_dimmer, + Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_dimmer, + Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS }) + ) + ) + + local ledBarComponent = mock_inovelli_dimmer.profile.components[LED_BAR_COMPONENT_NAME] + if ledBarComponent ~= nil then + test.socket.capability:__expect_send( + mock_inovelli_dimmer:generate_test_message( + LED_BAR_COMPONENT_NAME, + capabilities.colorControl.hue(1) + ) + ) + test.socket.capability:__expect_send( + mock_inovelli_dimmer:generate_test_message( + LED_BAR_COMPONENT_NAME, + capabilities.colorControl.saturation(1) + ) + ) + end end ) @@ -126,7 +152,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed({state_change = true})) + message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed({state_change = true})) } } ) @@ -150,7 +176,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed_4x({ state_change = true })) + message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed_4x({ state_change = true })) } } ) diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer.lua index 250182526d..d7dfe35b24 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_led.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_led.lua index 3141e5021f..6df8b97048 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_led.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_led.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_power_energy.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_power_energy.lua index 7605ada316..eab58c99f9 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_power_energy.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_power_energy.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_preferences.lua index 6b4e978981..43dd38ee7f 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_preferences.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_scenes.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_scenes.lua index 2f575d8e06..316b9b1254 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_scenes.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_dimmer_scenes.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" @@ -65,7 +54,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed({ + message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed({ state_change = true })) } } @@ -85,7 +74,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed_2x({ + message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed_2x({ state_change = true })) } } @@ -105,7 +94,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed_3x({ + message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed_3x({ state_change = true })) } } @@ -125,7 +114,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed_4x({ + message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed_4x({ state_change = true })) } } @@ -145,7 +134,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed_5x({ + message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed_5x({ state_change = true })) } } @@ -165,7 +154,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed({ + message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed({ state_change = true })) } } @@ -185,7 +174,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed_2x({ + message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed_2x({ state_change = true })) } } @@ -205,7 +194,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed_3x({ + message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed_3x({ state_change = true })) } } @@ -225,7 +214,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed_4x({ + message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed_4x({ state_change = true })) } } @@ -245,7 +234,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_inovelli_dimmer:generate_test_message("button2", capabilities.button.button.pushed_5x({ + message = mock_inovelli_dimmer:generate_test_message("button1", capabilities.button.button.pushed_5x({ state_change = true })) } } diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua new file mode 100644 index 0000000000..5593ced043 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua @@ -0,0 +1,325 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local capabilities = require "st.capabilities" +local zw = require "st.zwave" +local zw_test_utils = require "integration_test.zwave_test_utils" +local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({version=2}) +local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({version=4}) +local Basic = (require "st.zwave.CommandClass.Basic")({version=1}) +local CentralScene = (require "st.zwave.CommandClass.CentralScene")({version=3}) +local Association = (require "st.zwave.CommandClass.Association")({version=1}) +local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({version=7}) +local Meter = (require "st.zwave.CommandClass.Meter")({version=3}) +local Notification = (require "st.zwave.CommandClass.Notification")({version=3}) +local t_utils = require "integration_test.utils" + +-- Inovelli VZW32-SN device identifiers +local INOVELLI_MANUFACTURER_ID = 0x031E +local INOVELLI_VZW32_SN_PRODUCT_TYPE = 0x0017 +local INOVELLI_VZW32_SN_PRODUCT_ID = 0x0001 +local LED_BAR_COMPONENT_NAME = "LEDColorConfiguration" + +-- Device endpoints with supported command classes +local inovelli_vzw32_sn_endpoints = { + { + command_classes = { + {value = zw.SWITCH_BINARY}, + {value = zw.SWITCH_MULTILEVEL}, + {value = zw.BASIC}, + {value = zw.CONFIGURATION}, + {value = zw.CENTRAL_SCENE}, + {value = zw.ASSOCIATION}, + {value = zw.SENSOR_MULTILEVEL}, + {value = zw.METER}, + {value = zw.NOTIFICATION}, + } + } +} + +-- Create mock device +local mock_inovelli_vzw32_sn = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("inovelli-mmwave-dimmer-vzw32-sn.yml"), + zwave_endpoints = inovelli_vzw32_sn_endpoints, + zwave_manufacturer_id = INOVELLI_MANUFACTURER_ID, + zwave_product_type = INOVELLI_VZW32_SN_PRODUCT_TYPE, + zwave_product_id = INOVELLI_VZW32_SN_PRODUCT_ID +}) + +local function test_init() + test.mock_device.add_test_device(mock_inovelli_vzw32_sn) +end +test.set_test_init_function(test_init) + +local supported_button_values = { + ["button1"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button2"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + ["button3"] = {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"} +} + +-- Test device initialization +test.register_coroutine_test( + "Device should initialize properly on added lifecycle event", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_inovelli_vzw32_sn.id, "added" }) + + for button_name, _ in pairs(mock_inovelli_vzw32_sn.profile.components) do + if button_name ~= "main" and button_name ~= LED_BAR_COMPONENT_NAME then + test.socket.capability:__expect_send( + mock_inovelli_vzw32_sn:generate_test_message( + button_name, + capabilities.button.supportedButtonValues( + supported_button_values[button_name], + { visibility = { displayed = false } } + ) + ) + ) + test.socket.capability:__expect_send( + mock_inovelli_vzw32_sn:generate_test_message( + button_name, + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }) + ) + ) + end + end + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Association:Set({ + grouping_identifier = 1, + node_ids = {}, -- Mock hub Z-Wave ID + payload = "\x01", -- Should contain grouping_identifier = 1 + }) + ) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + SwitchMultilevel:Get({}) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.ILLUMINANCE}) + ) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) + ) + ) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS }) + ) + ) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Notification:Get({notification_type = Notification.notification_type.HOME_SECURITY, event = Notification.event.home_security.MOTION_DETECTION}) + ) + ) + end +) + +-- Test switch on command +test.register_coroutine_test( + "Switch on command should send Basic Set with ON value", + function() + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + test.socket.capability:__queue_receive({ + mock_inovelli_vzw32_sn.id, + { capability = "switch", command = "on", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Basic:Set({ value = SwitchBinary.value.ON_ENABLE }) + ) + ) + test.wait_for_events() + test.mock_time.advance_time(3) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + SwitchMultilevel:Get({}) + ) + ) + end +) + +-- Test switch off command +test.register_coroutine_test( + "Switch off command should send Basic Set with OFF value", + function() + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + test.socket.capability:__queue_receive({ + mock_inovelli_vzw32_sn.id, + { capability = "switch", command = "off", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Basic:Set({ value = SwitchBinary.value.OFF_DISABLE }) + ) + ) + test.wait_for_events() + test.mock_time.advance_time(3) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + SwitchMultilevel:Get({}) + ) + ) + end +) + +-- Test switch level command +test.register_coroutine_test( + "Switch level command should send SwitchMultilevel Set", + function() + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + + test.socket.capability:__queue_receive({ + mock_inovelli_vzw32_sn.id, + { capability = "switchLevel", command = "setLevel", args = { 50 } } + }) + + local expected_command = SwitchMultilevel:Set({ value = 50, duration = "default" }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + expected_command + ) + ) + + test.wait_for_events() + test.mock_time.advance_time(3) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + SwitchMultilevel:Get({}) + ) + ) + end +) + +-- Test central scene notifications +test.register_message_test( + "Central scene notification should emit button events", + { + { + channel = "zwave", + direction = "receive", + message = { mock_inovelli_vzw32_sn.id, zw_test_utils.zwave_test_build_receive_command(CentralScene:Notification({ + scene_number = 1, + key_attributes=CentralScene.key_attributes.KEY_PRESSED_1_TIME + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzw32_sn:generate_test_message("button1", capabilities.button.button.pushed({ + state_change = true + })) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test central scene notifications - button2 pressed 4 times +test.register_message_test( + "Central scene notification button2 pressed 4 times should emit button events", + { + { + channel = "zwave", + direction = "receive", + message = { mock_inovelli_vzw32_sn.id, zw_test_utils.zwave_test_build_receive_command(CentralScene:Notification({ + scene_number = 2, + key_attributes=CentralScene.key_attributes.KEY_PRESSED_4_TIMES + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_inovelli_vzw32_sn:generate_test_message("button2", capabilities.button.button.pushed_4x({ + state_change = true + })) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test refresh capability +test.register_message_test( + "Refresh capability should request switch level", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzw32_sn.id, + { capability = "refresh", command = "refresh", args = {} } + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + SwitchMultilevel:Get({}) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + SensorMultilevel:Get({sensor_type = SensorMultilevel.sensor_type.ILLUMINANCE}) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Meter:Get({ scale = Meter.scale.electric_meter.WATTS }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Meter:Get({ scale = Meter.scale.electric_meter.KILOWATT_HOURS }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Notification:Get({notification_type = Notification.notification_type.HOME_SECURITY, event = Notification.event.home_security.MOTION_DETECTION}) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua new file mode 100644 index 0000000000..40213851c7 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua @@ -0,0 +1,335 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local capabilities = require "st.capabilities" +local zw = require "st.zwave" +local zw_test_utils = require "integration_test.zwave_test_utils" +local Configuration = (require "st.zwave.CommandClass.Configuration")({version=4}) +local t_utils = require "integration_test.utils" +local st_device = require "st.device" + +-- Inovelli VZW32-SN device identifiers +local INOVELLI_MANUFACTURER_ID = 0x031E +local INOVELLI_VZW32_SN_PRODUCT_TYPE = 0x0017 +local INOVELLI_VZW32_SN_PRODUCT_ID = 0x0001 + +-- Device endpoints with supported command classes +local inovelli_vzw32_sn_endpoints = { + { + command_classes = { + {value = zw.SWITCH_BINARY}, + {value = zw.SWITCH_MULTILEVEL}, + {value = zw.BASIC}, + {value = zw.CONFIGURATION}, + {value = zw.CENTRAL_SCENE}, + {value = zw.ASSOCIATION}, + } + } +} + +-- Create mock parent device +local mock_parent_device = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("inovelli-mmwave-dimmer-vzw32-sn.yml"), + zwave_endpoints = inovelli_vzw32_sn_endpoints, + zwave_manufacturer_id = INOVELLI_MANUFACTURER_ID, + zwave_product_type = INOVELLI_VZW32_SN_PRODUCT_TYPE, + zwave_product_id = INOVELLI_VZW32_SN_PRODUCT_ID +}) + +-- Create mock child device (notification device) +local mock_child_device = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("rgbw-bulb.yml"), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = "notification" +}) + +-- Set child device network type +mock_child_device.network_type = st_device.NETWORK_TYPE_CHILD + +local function test_init() + test.mock_device.add_test_device(mock_parent_device) + test.mock_device.add_test_device(mock_child_device) +end +test.set_test_init_function(test_init) + +-- Test child device initialization +test.register_message_test( + "Child device should initialize with default color values", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_child_device.id, "added" }, + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorControl.hue(1)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorControl.saturation(1)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = 2700, maximum = 6500} })) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.switchLevel.level(100)) + }, + { + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.switch.switch("off")) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test child device switch on command +test.register_coroutine_test( + "Child device switch on should emit events and send configuration to parent", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local level = 100 -- Default level for child devices + local color = 100 -- Default color for child devices (since device starts with no hue state) + local effect = 1 -- Default notificationType + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switch", command = "on", args = {} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent_device, + Configuration:Set({ + parameter_number = 99, + configuration_value = notificationValue, + size = 4 + }) + ) + ) + end +) + +-- Test child device switch off command +test.register_coroutine_test( + "Child device switch off should emit events and send configuration to parent", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switch", command = "off", args = {} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("off")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent_device, + Configuration:Set({ + parameter_number = 99, + configuration_value = 0, -- Switch off sends 0 + size = 4 + }) + ) + ) + end +) + +-- Test child device level command +test.register_coroutine_test( + "Child device level command should emit events and send configuration to parent", + function() + local level = math.random(1, 99) + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local effect = 1 -- Default notificationType + local color = 100 -- Default color for child devices (since device starts with no hue state) + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) -- Use the actual level from command + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switchLevel", command = "setLevel", args = { level } } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switchLevel.level(level)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent_device, + Configuration:Set({ + parameter_number = 99, + configuration_value = notificationValue, + size = 4 + }) + ) + ) + end +) + +-- Test child device color command +test.register_coroutine_test( + "Child device color command should emit events and send configuration to parent", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local level = 100 -- Default level for child devices + local color = math.random(0, 100) -- Default color for child devices (since device starts with no hue state) + local effect = 1 -- Default notificationType + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "colorControl", command = "setColor", args = {{ hue = color, saturation = 100 }} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.hue(color)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.saturation(100)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent_device, + Configuration:Set({ + parameter_number = 99, + configuration_value = notificationValue, + size = 4 + }) + ) + ) + end +) + +-- Test child device color temperature command +test.register_coroutine_test( + "Child device color temperature command should emit events and send configuration to parent", + function() + local temp = math.random(2700, 6500) + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "colorTemperature", command = "setColorTemperature", args = { temp } } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.hue(100)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(temp)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_parent_device, + Configuration:Set({ + parameter_number = 99, + configuration_value = 33514751, -- Calculated: effect(1)*16777216 + hue(255)*65536 + level(100)*256 + 255 + size = 4 + }) + ) + ) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua new file mode 100644 index 0000000000..8cccb5726e --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua @@ -0,0 +1,151 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local zw = require "st.zwave" +local zw_test_utils = require "integration_test.zwave_test_utils" +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) +local t_utils = require "integration_test.utils" + +-- Inovelli VZW32-SN device identifiers +local INOVELLI_MANUFACTURER_ID = 0x031E +local INOVELLI_VZW32_SN_PRODUCT_TYPE = 0x0017 +local INOVELLI_VZW32_SN_PRODUCT_ID = 0x0001 + +-- Device endpoints with supported command classes +local inovelli_vzw32_sn_endpoints = { + { + command_classes = { + { value = zw.SWITCH_BINARY }, + { value = zw.SWITCH_MULTILEVEL }, + { value = zw.BASIC }, + { value = zw.CONFIGURATION }, + { value = zw.CENTRAL_SCENE }, + { value = zw.ASSOCIATION }, + } + } +} + +-- Create mock device +local mock_inovelli_vzw32_sn = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("inovelli-mmwave-dimmer-vzw32-sn.yml"), + zwave_endpoints = inovelli_vzw32_sn_endpoints, + zwave_manufacturer_id = INOVELLI_MANUFACTURER_ID, + zwave_product_type = INOVELLI_VZW32_SN_PRODUCT_TYPE, + zwave_product_id = INOVELLI_VZW32_SN_PRODUCT_ID +}) + +local function test_init() + test.mock_device.add_test_device(mock_inovelli_vzw32_sn) +end +test.set_test_init_function(test_init) + +-- Test parameter 1 (example preference) +do + local new_param_value = 10 + test.register_coroutine_test( + "Parameter 1 should be updated in the device configuration after change", + function() + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzw32_sn:generate_info_changed({preferences = {parameter1 = new_param_value}})) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 1, + configuration_value = new_param_value, + size = 1 + }) + ) + ) + end + ) +end + +-- Test parameter 52 (example preference) +do + local new_param_value = 25 + test.register_coroutine_test( + "Parameter 52 should be updated in the device configuration after change", + function() + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzw32_sn:generate_info_changed({preferences = {parameter52 = new_param_value}})) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 52, + configuration_value = new_param_value, + size = 1 + }) + ) + ) + end + ) +end + +-- Test parameter 158 (example preference) +do + local new_param_value = 5 + test.register_coroutine_test( + "Parameter 158 should be updated in the device configuration after change", + function() + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzw32_sn:generate_info_changed({preferences = {parameter158 = new_param_value}})) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 158, + configuration_value = new_param_value, + size = 1 + }) + ) + ) + end + ) +end + +-- Test parameter 101 (2-byte parameter) +do + local new_param_value = -400 + test.register_coroutine_test( + "Parameter 101 should be updated in the device configuration after change", + function() + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzw32_sn:generate_info_changed({preferences = {parameter101 = new_param_value}})) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 101, + configuration_value = new_param_value, + size = 2 + }) + ) + ) + end + ) +end + +-- Test notificationChild preference (special case for child device creation) +do + local new_param_value = true + test.register_coroutine_test( + "notificationChild preference should create child device when enabled", + function() + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzw32_sn:generate_info_changed({preferences = {notificationChild = new_param_value}})) + + -- Expect child device creation + mock_inovelli_vzw32_sn:expect_device_create({ + type = "EDGE_CHILD", + label = "nil Notification", -- This will be the parent label + "Notification" + profile = "rgbw-bulb", + parent_device_id = mock_inovelli_vzw32_sn.id, + parent_assigned_child_key = "notification" + }) + end + ) +end + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/src/test/test_multi_metering_switch.lua b/drivers/SmartThings/zwave-switch/src/test/test_multi_metering_switch.lua index 9373383093..11a79bc12d 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_multi_metering_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_multi_metering_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_multichannel_device.lua b/drivers/SmartThings/zwave-switch/src/test/test_multichannel_device.lua index 4c7d4c8583..7f584e3125 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_multichannel_device.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_multichannel_device.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" local zw = require "st.zwave" @@ -2525,4 +2514,4 @@ test.register_coroutine_test( end ) -test.run_registered_tests() \ No newline at end of file +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-switch/src/test/test_popp_outdoor_plug_configuration.lua b/drivers/SmartThings/zwave-switch/src/test/test_popp_outdoor_plug_configuration.lua index 20b0eed8f6..165dab059e 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_popp_outdoor_plug_configuration.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_popp_outdoor_plug_configuration.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_qubino_din_dimmer.lua b/drivers/SmartThings/zwave-switch/src/test/test_qubino_din_dimmer.lua index bd846c93b2..9cec4ecd4e 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_qubino_din_dimmer.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_qubino_din_dimmer.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_qubino_din_dimmer_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_qubino_din_dimmer_preferences.lua index 21e7fbba3f..920c6c5b2f 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_qubino_din_dimmer_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_qubino_din_dimmer_preferences.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_1_relay_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_1_relay_preferences.lua index d9e6b82d63..d44eb8f7cb 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_1_relay_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_1_relay_preferences.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_1d_relay_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_1d_relay_preferences.lua index ba758717e8..3436ccba26 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_1d_relay_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_1d_relay_preferences.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_2_relay.lua b/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_2_relay.lua index 8481055813..61a62ef0d7 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_2_relay.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_2_relay.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_2_relay_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_2_relay_preferences.lua index 4fc60d1927..3ac1fda4b4 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_2_relay_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_2_relay_preferences.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_dimmer.lua b/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_dimmer.lua index 77663604d5..cfe8dccde7 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_dimmer.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_dimmer.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_dimmer_0_10V_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_dimmer_0_10V_preferences.lua index 48fa00fa38..db28991f2d 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_dimmer_0_10V_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_dimmer_0_10V_preferences.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_dimmer_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_dimmer_preferences.lua index cd07b64151..d88d6c39b1 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_dimmer_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_qubino_flush_dimmer_preferences.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_qubino_mini_dimmer_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_qubino_mini_dimmer_preferences.lua index a4f60c6a50..f385e0be33 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_qubino_mini_dimmer_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_qubino_mini_dimmer_preferences.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_qubino_temperature_sensor_with_power.lua b/drivers/SmartThings/zwave-switch/src/test/test_qubino_temperature_sensor_with_power.lua index 6f4a098ef7..ccaf9c7aec 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_qubino_temperature_sensor_with_power.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_qubino_temperature_sensor_with_power.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_qubino_temperature_sensor_without_power.lua b/drivers/SmartThings/zwave-switch/src/test/test_qubino_temperature_sensor_without_power.lua index 83f6674852..c06f516249 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_qubino_temperature_sensor_without_power.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_qubino_temperature_sensor_without_power.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_shelly_multi_metering_switch.lua b/drivers/SmartThings/zwave-switch/src/test/test_shelly_multi_metering_switch.lua index b5606f6ab9..331640b1be 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_shelly_multi_metering_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_shelly_multi_metering_switch.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_wyfy_touch.lua b/drivers/SmartThings/zwave-switch/src/test/test_wyfy_touch.lua index cc34fe0737..9bc1387f2b 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_wyfy_touch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_wyfy_touch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_wyfy_touch_configuration.lua b/drivers/SmartThings/zwave-switch/src/test/test_wyfy_touch_configuration.lua index 0a295fbd91..010607fc2d 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_wyfy_touch_configuration.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_wyfy_touch_configuration.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_zooz_double_plug.lua b/drivers/SmartThings/zwave-switch/src/test/test_zooz_double_plug.lua index be1b64b05b..abf41946cc 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zooz_double_plug.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zooz_double_plug.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_zooz_power_strip.lua b/drivers/SmartThings/zwave-switch/src/test/test_zooz_power_strip.lua index d63c644040..477886d76e 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zooz_power_strip.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zooz_power_strip.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_zooz_zen_30_dimmer_relay.lua b/drivers/SmartThings/zwave-switch/src/test/test_zooz_zen_30_dimmer_relay.lua index 926733ab34..b3a18291e7 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zooz_zen_30_dimmer_relay.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zooz_zen_30_dimmer_relay.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local zw = require "st.zwave" local test = require "integration_test" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_zooz_zen_30_dimmer_relay_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_zooz_zen_30_dimmer_relay_preferences.lua index 7ebf45335a..c186f9d36b 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zooz_zen_30_dimmer_relay_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zooz_zen_30_dimmer_relay_preferences.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_zwave_dimmer_power_energy.lua b/drivers/SmartThings/zwave-switch/src/test/test_zwave_dimmer_power_energy.lua index 35f8b341dd..215cc0675d 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zwave_dimmer_power_energy.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zwave_dimmer_power_energy.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local t_utils = require "integration_test.utils" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_zwave_dual_switch.lua b/drivers/SmartThings/zwave-switch/src/test/test_zwave_dual_switch.lua index 4b69b063a7..84502e9835 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zwave_dual_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zwave_dual_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_zwave_dual_switch_migration.lua b/drivers/SmartThings/zwave-switch/src/test/test_zwave_dual_switch_migration.lua index db799e9451..ac396439f6 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zwave_dual_switch_migration.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zwave_dual_switch_migration.lua @@ -1,16 +1,5 @@ --- 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch.lua b/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch.lua index d1d73cd496..effb5845b4 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_battery.lua b/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_battery.lua index acba87f120..26a72eb4e6 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_battery.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_battery.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_electric_meter.lua b/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_electric_meter.lua index 13d3e90a3c..935e4b63e8 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_electric_meter.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_electric_meter.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_energy_meter.lua b/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_energy_meter.lua index 2a96961755..016cb56ef1 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_energy_meter.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_energy_meter.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_level.lua b/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_level.lua index b8e834cffe..c8cca92dfb 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_level.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_level.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_power_meter.lua b/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_power_meter.lua index 1905fec5f6..edec7aefba 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_power_meter.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_zwave_switch_power_meter.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-switch/src/zooz-power-strip/can_handle.lua b/drivers/SmartThings/zwave-switch/src/zooz-power-strip/can_handle.lua new file mode 100644 index 0000000000..a55c1120e6 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/zooz-power-strip/can_handle.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local function can_handle_zooz_power_strip(opts, driver, device, ...) + local fingerprints = { + {mfr = 0x015D, prod = 0x0651, model = 0xF51C} -- Zooz ZEN 20 Power Strip + } + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("zooz-power-strip") + return true, subdriver + end + end + return false +end + +return can_handle_zooz_power_strip diff --git a/drivers/SmartThings/zwave-switch/src/zooz-power-strip/init.lua b/drivers/SmartThings/zwave-switch/src/zooz-power-strip/init.lua index 24b969d13e..8bf3ad9dc4 100644 --- a/drivers/SmartThings/zwave-switch/src/zooz-power-strip/init.lua +++ b/drivers/SmartThings/zwave-switch/src/zooz-power-strip/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -20,20 +9,6 @@ local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) --- @type st.zwave.CommandClass.SwitchBinary local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({ version = 2 }) -local ZOOZ_POWER_STRIP_FINGERPRINTS = { - {mfr = 0x015D, prod = 0x0651, model = 0xF51C} -- Zooz ZEN 20 Power Strip -} - -local function can_handle_zooz_power_strip(opts, driver, device, ...) - for _, fingerprint in ipairs(ZOOZ_POWER_STRIP_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("zooz-power-strip") - return true, subdriver - end - end - return false -end - local function binary_event_helper(driver, device, cmd) if cmd.src_channel > 0 then local value = cmd.args.value and cmd.args.value or cmd.args.target_value @@ -124,7 +99,7 @@ local zooz_power_strip = { [capabilities.switch.commands.off.NAME] = switch_off_handler } }, - can_handle = can_handle_zooz_power_strip, + can_handle = require("zooz-power-strip.can_handle"), } return zooz_power_strip diff --git a/drivers/SmartThings/zwave-switch/src/zooz-zen-30-dimmer-relay/can_handle.lua b/drivers/SmartThings/zwave-switch/src/zooz-zen-30-dimmer-relay/can_handle.lua new file mode 100644 index 0000000000..19d44bc623 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/zooz-zen-30-dimmer-relay/can_handle.lua @@ -0,0 +1,17 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_zooz_zen_30_dimmer_relay_double_switch(opts, driver, device, ...) + local fingerprints = { + { mfr = 0x027A, prod = 0xA000, model = 0xA008 } -- Zooz Zen 30 Dimmer Relay Double Switch + } + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("zooz-zen-30-dimmer-relay") + return true, subdriver + end + end + return false +end + +return can_handle_zooz_zen_30_dimmer_relay_double_switch diff --git a/drivers/SmartThings/zwave-switch/src/zooz-zen-30-dimmer-relay/init.lua b/drivers/SmartThings/zwave-switch/src/zooz-zen-30-dimmer-relay/init.lua index c92c485e43..6bb9bd4e1b 100644 --- a/drivers/SmartThings/zwave-switch/src/zooz-zen-30-dimmer-relay/init.lua +++ b/drivers/SmartThings/zwave-switch/src/zooz-zen-30-dimmer-relay/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local st_device = require "st.device" local capabilities = require "st.capabilities" local cc = require "st.zwave.CommandClass" @@ -70,20 +59,6 @@ local map_key_attribute_to_capability = { } } -local ZOOZ_ZEN_30_DIMMER_RELAY_FINGERPRINTS = { - { mfr = 0x027A, prod = 0xA000, model = 0xA008 } -- Zooz Zen 30 Dimmer Relay Double Switch -} - -local function can_handle_zooz_zen_30_dimmer_relay_double_switch(opts, driver, device, ...) - for _, fingerprint in ipairs(ZOOZ_ZEN_30_DIMMER_RELAY_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("zooz-zen-30-dimmer-relay") - return true, subdriver - end - end - return false -end - local function find_child(parent, src_channel) if src_channel == 0 then return parent @@ -205,7 +180,7 @@ local zooz_zen_30_dimmer_relay_double_switch = { init = device_init, added = device_added }, - can_handle = can_handle_zooz_zen_30_dimmer_relay_double_switch + can_handle = require("zooz-zen-30-dimmer-relay.can_handle") } return zooz_zen_30_dimmer_relay_double_switch diff --git a/drivers/SmartThings/zwave-switch/src/zwave-dual-switch/can_handle.lua b/drivers/SmartThings/zwave-switch/src/zwave-dual-switch/can_handle.lua new file mode 100644 index 0000000000..416c3dfa67 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/zwave-dual-switch/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +local function can_handle_zwave_dual_switch(opts, driver, device, ...) + local fingerprints = require("zwave-dual-switch.fingerprints") + for _, fingerprint in ipairs(fingerprints) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("zwave-dual-switch") + return true, subdriver + end + end + return false +end + +return can_handle_zwave_dual_switch diff --git a/drivers/SmartThings/zwave-switch/src/zwave-dual-switch/dual_switch_configurations.lua b/drivers/SmartThings/zwave-switch/src/zwave-dual-switch/dual_switch_configurations.lua index d0b2f83373..f5c01ecf6b 100644 --- a/drivers/SmartThings/zwave-switch/src/zwave-dual-switch/dual_switch_configurations.lua +++ b/drivers/SmartThings/zwave-switch/src/zwave-dual-switch/dual_switch_configurations.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local devices = { FIBARO_WALLI_DOUBLE_SWITCH = { diff --git a/drivers/SmartThings/zwave-switch/src/zwave-dual-switch/fingerprints.lua b/drivers/SmartThings/zwave-switch/src/zwave-dual-switch/fingerprints.lua new file mode 100644 index 0000000000..9640f5c4d2 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/zwave-dual-switch/fingerprints.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = 0x0086, prod = 0x0103, model = 0x008C }, -- Aeotec Switch 1 + { mfr = 0x0086, prod = 0x0003, model = 0x008C }, -- Aeotec Switch 1 + { mfr = 0x0258, prod = 0x0003, model = 0x008B }, -- NEO Coolcam Switch 1 + { mfr = 0x0258, prod = 0x0003, model = 0x108B }, -- NEO Coolcam Switch 1 + { mfr = 0x0312, prod = 0xC000, model = 0xC004 }, -- EVA Switch 1 + { mfr = 0x0312, prod = 0xFF00, model = 0xFF05 }, -- Minoston Switch 1 + { mfr = 0x0312, prod = 0xC000, model = 0xC007 }, -- Evalogik Switch 1 + { mfr = 0x010F, prod = 0x1B01, model = 0x1000 }, -- Fibaro Walli Double Switch + { mfr = 0x027A, prod = 0xA000, model = 0xA003 } -- Zooz Double Plug +} diff --git a/drivers/SmartThings/zwave-switch/src/zwave-dual-switch/init.lua b/drivers/SmartThings/zwave-switch/src/zwave-dual-switch/init.lua index 60bf54e4e0..4bea42c584 100644 --- a/drivers/SmartThings/zwave-switch/src/zwave-dual-switch/init.lua +++ b/drivers/SmartThings/zwave-switch/src/zwave-dual-switch/init.lua @@ -1,16 +1,5 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 local st_device = require "st.device" local capabilities = require "st.capabilities" --- @type st.zwave.defaults.switch @@ -26,28 +15,6 @@ local Meter = (require "st.zwave.CommandClass.Meter")({ version = 3 }) local dualSwitchConfigurationsMap = require "zwave-dual-switch/dual_switch_configurations" local utils = require "st.utils" -local ZWAVE_DUAL_SWITCH_FINGERPRINTS = { - { mfr = 0x0086, prod = 0x0103, model = 0x008C }, -- Aeotec Switch 1 - { mfr = 0x0086, prod = 0x0003, model = 0x008C }, -- Aeotec Switch 1 - { mfr = 0x0258, prod = 0x0003, model = 0x008B }, -- NEO Coolcam Switch 1 - { mfr = 0x0258, prod = 0x0003, model = 0x108B }, -- NEO Coolcam Switch 1 - { mfr = 0x0312, prod = 0xC000, model = 0xC004 }, -- EVA Switch 1 - { mfr = 0x0312, prod = 0xFF00, model = 0xFF05 }, -- Minoston Switch 1 - { mfr = 0x0312, prod = 0xC000, model = 0xC007 }, -- Evalogik Switch 1 - { mfr = 0x010F, prod = 0x1B01, model = 0x1000 }, -- Fibaro Walli Double Switch - { mfr = 0x027A, prod = 0xA000, model = 0xA003 } -- Zooz Double Plug -} - -local function can_handle_zwave_dual_switch(opts, driver, device, ...) - for _, fingerprint in ipairs(ZWAVE_DUAL_SWITCH_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - local subdriver = require("zwave-dual-switch") - return true, subdriver - end - end - return false -end - local function find_child(parent, src_channel) if src_channel == 1 then return parent @@ -166,7 +133,7 @@ local zwave_dual_switch = { added = device_added, init = device_init }, - can_handle = can_handle_zwave_dual_switch + can_handle = require("zwave-dual-switch.can_handle") } return zwave_dual_switch diff --git a/drivers/SmartThings/zwave-thermostat/src/aeotec-radiator-thermostat/can_handle.lua b/drivers/SmartThings/zwave-thermostat/src/aeotec-radiator-thermostat/can_handle.lua new file mode 100644 index 0000000000..9feb63e1fa --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/aeotec-radiator-thermostat/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_aeotec_radiator_thermostat(opts, driver, device, ...) + local AEOTEC_THERMOSTAT_FINGERPRINT = {mfr = 0x0371, prod = 0x0002, model = 0x0015} + + if device:id_match(AEOTEC_THERMOSTAT_FINGERPRINT.mfr, AEOTEC_THERMOSTAT_FINGERPRINT.prod, AEOTEC_THERMOSTAT_FINGERPRINT.model) then + return true, require "aeotec-radiator-thermostat" + else + return false + end +end + +return can_handle_aeotec_radiator_thermostat diff --git a/drivers/SmartThings/zwave-thermostat/src/aeotec-radiator-thermostat/init.lua b/drivers/SmartThings/zwave-thermostat/src/aeotec-radiator-thermostat/init.lua index b101a43d38..cdb90e7f44 100755 --- a/drivers/SmartThings/zwave-thermostat/src/aeotec-radiator-thermostat/init.lua +++ b/drivers/SmartThings/zwave-thermostat/src/aeotec-radiator-thermostat/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -24,12 +14,6 @@ local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ver --- @type st.zwave.CommandClass.ThermostatSetpoint local ThermostatSetpoint = (require "st.zwave.CommandClass.ThermostatSetpoint")({version=1}) -local AEOTEC_THERMOSTAT_FINGERPRINT = {mfr = 0x0371, prod = 0x0002, model = 0x0015} - -local function can_handle_aeotec_radiator_thermostat(opts, driver, device, ...) - return device:id_match(AEOTEC_THERMOSTAT_FINGERPRINT.mfr, AEOTEC_THERMOSTAT_FINGERPRINT.prod, AEOTEC_THERMOSTAT_FINGERPRINT.model) -end - local function thermostat_mode_report_handler(self, device, cmd) local event = nil if (cmd.args.mode == ThermostatMode.mode.OFF) then @@ -114,7 +98,7 @@ local aeotec_radiator_thermostat = { } }, lifecycle_handlers = {added = device_added}, - can_handle = can_handle_aeotec_radiator_thermostat + can_handle = require("aeotec-radiator-thermostat.can_handle"), } return aeotec_radiator_thermostat diff --git a/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/can_handle.lua b/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/can_handle.lua new file mode 100644 index 0000000000..079eeee5d3 --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/can_handle.lua @@ -0,0 +1,25 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle(opts, driver, device, cmd, ...) + local version = require "version" + local cc = require "st.zwave.CommandClass" + local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) + local DANFOSS_LC13_THERMOSTAT_FPS = require "apiv6_bugfix.fingerprints" + + if version.api == 6 and + cmd.cmd_class == cc.WAKE_UP and + cmd.cmd_id == WakeUp.NOTIFICATION and not + (device:id_match(DANFOSS_LC13_THERMOSTAT_FPS[1].manufacturerId, + DANFOSS_LC13_THERMOSTAT_FPS[1].productType, + DANFOSS_LC13_THERMOSTAT_FPS[1].productId) or + device:id_match(DANFOSS_LC13_THERMOSTAT_FPS[2].manufacturerId, + DANFOSS_LC13_THERMOSTAT_FPS[2].productType, + DANFOSS_LC13_THERMOSTAT_FPS[2].productId)) then + return true, require "apiv6_bugfix" + else + return false + end +end + +return can_handle diff --git a/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/fingerprints.lua b/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/fingerprints.lua new file mode 100644 index 0000000000..e87a5990e2 --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local DANFOSS_LC13_THERMOSTAT_FPS = { + { manufacturerId = 0x0002, productType = 0x0005, productId = 0x0003 }, -- Danfoss LC13 Thermostat + { manufacturerId = 0x0002, productType = 0x0005, productId = 0x0004 } -- Danfoss LC13 Thermostat +} + +return DANFOSS_LC13_THERMOSTAT_FPS diff --git a/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/init.lua b/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/init.lua index 4994710970..52c419a590 100644 --- a/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/init.lua +++ b/drivers/SmartThings/zwave-thermostat/src/apiv6_bugfix/init.lua @@ -1,23 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local cc = require "st.zwave.CommandClass" local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 }) -local DANFOSS_LC13_THERMOSTAT_FPS = { - { manufacturerId = 0x0002, productType = 0x0005, productId = 0x0003 }, -- Danfoss LC13 Thermostat - { manufacturerId = 0x0002, productType = 0x0005, productId = 0x0004 } -- Danfoss LC13 Thermostat -} - -local function can_handle(opts, driver, device, cmd, ...) - local version = require "version" - return version.api == 6 and - cmd.cmd_class == cc.WAKE_UP and - cmd.cmd_id == WakeUp.NOTIFICATION and not - (device:id_match(DANFOSS_LC13_THERMOSTAT_FPS[1].manufacturerId, - DANFOSS_LC13_THERMOSTAT_FPS[1].productType, - DANFOSS_LC13_THERMOSTAT_FPS[1].productId) or - device:id_match(DANFOSS_LC13_THERMOSTAT_FPS[2].manufacturerId, - DANFOSS_LC13_THERMOSTAT_FPS[2].productType, - DANFOSS_LC13_THERMOSTAT_FPS[2].productId)) -end local function wakeup_notification(driver, device, cmd) device:refresh() @@ -30,7 +16,7 @@ local apiv6_bugfix = { } }, NAME = "apiv6_bugfix", - can_handle = can_handle + can_handle = require("apiv6_bugfix.can_handle"), } return apiv6_bugfix diff --git a/drivers/SmartThings/zwave-thermostat/src/ct100-thermostat/can_handle.lua b/drivers/SmartThings/zwave-thermostat/src/ct100-thermostat/can_handle.lua new file mode 100644 index 0000000000..6483fc2393 --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/ct100-thermostat/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_ct100_thermostat(opts, driver, device) + local CT100_THERMOSTAT_FINGERPRINTS = require "ct100-thermostat.fingerprints" + for _, fingerprint in ipairs(CT100_THERMOSTAT_FINGERPRINTS) do + if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require "ct100-thermostat" + end + end + + return false +end + +return can_handle_ct100_thermostat diff --git a/drivers/SmartThings/zwave-thermostat/src/ct100-thermostat/fingerprints.lua b/drivers/SmartThings/zwave-thermostat/src/ct100-thermostat/fingerprints.lua new file mode 100644 index 0000000000..c45eb5e7af --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/ct100-thermostat/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local CT100_THERMOSTAT_FINGERPRINTS = { + { manufacturerId = 0x0098, productType = 0x6401, productId = 0x0107 }, -- 2Gig CT100 Programmable Thermostat + { manufacturerId = 0x0098, productType = 0x6501, productId = 0x000C }, -- Iris Thermostat +} + +return CT100_THERMOSTAT_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-thermostat/src/ct100-thermostat/init.lua b/drivers/SmartThings/zwave-thermostat/src/ct100-thermostat/init.lua index e44da0ca36..d5859fe777 100644 --- a/drivers/SmartThings/zwave-thermostat/src/ct100-thermostat/init.lua +++ b/drivers/SmartThings/zwave-thermostat/src/ct100-thermostat/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local cc = require "st.zwave.CommandClass" @@ -33,11 +23,6 @@ local cooling_setpoint_defaults = require "st.zwave.defaults.thermostatCoolingSe local constants = require "st.zwave.constants" local utils = require "st.utils" -local CT100_THERMOSTAT_FINGERPRINTS = { - { manufacturerId = 0x0098, productType = 0x6401, productId = 0x0107 }, -- 2Gig CT100 Programmable Thermostat - { manufacturerId = 0x0098, productType = 0x6501, productId = 0x000C }, -- Iris Thermostat -} - -- This old device uses separate endpoints to get values of temp and humidity -- DTH actually uses the old mutliInstance encap, but multichannel should be back-compat local TEMPERATURE_ENDPOINT = 1 @@ -75,16 +60,6 @@ local function set_setpoint_factory(setpoint_type) end end -local function can_handle_ct100_thermostat(opts, driver, device) - for _, fingerprint in ipairs(CT100_THERMOSTAT_FINGERPRINTS) do - if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - - return false -end - local function thermostat_mode_report_handler(self, device, cmd) local event = nil @@ -210,7 +185,7 @@ local ct100_thermostat = { [capabilities.refresh.commands.refresh.NAME] = do_refresh } }, - can_handle = can_handle_ct100_thermostat, + can_handle = require("ct100-thermostat.can_handle"), } return ct100_thermostat diff --git a/drivers/SmartThings/zwave-thermostat/src/fibaro-heat-controller/can_handle.lua b/drivers/SmartThings/zwave-thermostat/src/fibaro-heat-controller/can_handle.lua new file mode 100644 index 0000000000..6ece30f897 --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/fibaro-heat-controller/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_heat_controller(opts, driver, device, ...) + local FINGERPRINTS = require("fibaro-heat-controller.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("fibaro-heat-controller") + end + end + + return false +end + +return can_handle_fibaro_heat_controller diff --git a/drivers/SmartThings/zwave-thermostat/src/fibaro-heat-controller/fingerprints.lua b/drivers/SmartThings/zwave-thermostat/src/fibaro-heat-controller/fingerprints.lua new file mode 100644 index 0000000000..31cbd7ff0e --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/fibaro-heat-controller/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FIBARO_HEAT_FINGERPRINTS = { + {mfr = 0x010F, prod = 0x1301, model = 0x1000}, -- Fibaro Heat Controller + {mfr = 0x010F, prod = 0x1301, model = 0x1001} -- Fibaro Heat Controller +} + +return FIBARO_HEAT_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-thermostat/src/fibaro-heat-controller/init.lua b/drivers/SmartThings/zwave-thermostat/src/fibaro-heat-controller/init.lua index be5365edf0..ab7ef51f30 100644 --- a/drivers/SmartThings/zwave-thermostat/src/fibaro-heat-controller/init.lua +++ b/drivers/SmartThings/zwave-thermostat/src/fibaro-heat-controller/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -30,22 +20,9 @@ local ApplicationStatus = (require "st.zwave.CommandClass.ApplicationStatus")({v local utils = require "st.utils" -local FIBARO_HEAT_FINGERPRINTS = { - {mfr = 0x010F, prod = 0x1301, model = 0x1000}, -- Fibaro Heat Controller - {mfr = 0x010F, prod = 0x1301, model = 0x1001} -- Fibaro Heat Controller -} local FORCED_REFRESH_THREAD = "forcedRefreshThread" -local function can_handle_fibaro_heat_controller(opts, driver, device, ...) - for _, fingerprint in ipairs(FIBARO_HEAT_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - - return false -end local function thermostat_mode_report_handler(self, device, cmd) local event = nil @@ -189,7 +166,7 @@ local fibaro_heat_controller = { added = device_added, init = map_components }, - can_handle = can_handle_fibaro_heat_controller + can_handle = require("fibaro-heat-controller.can_handle"), } return fibaro_heat_controller diff --git a/drivers/SmartThings/zwave-thermostat/src/init.lua b/drivers/SmartThings/zwave-thermostat/src/init.lua index 4fb3eff09e..3668085b1a 100755 --- a/drivers/SmartThings/zwave-thermostat/src/init.lua +++ b/drivers/SmartThings/zwave-thermostat/src/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.Driver @@ -115,16 +105,7 @@ local driver_template = { lifecycle_handlers = { added = device_added }, - sub_drivers = { - require("aeotec-radiator-thermostat"), - require("popp-radiator-thermostat"), - require("ct100-thermostat"), - require("fibaro-heat-controller"), - require("stelpro-ki-thermostat"), - require("qubino-flush-thermostat"), - require("thermostat-heating-battery"), - require("apiv6_bugfix"), - } + sub_drivers = require("sub_drivers"), } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities, {native_capability_attrs_enabled = true}) diff --git a/drivers/SmartThings/zwave-thermostat/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-thermostat/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..45115081e4 --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/lazy_load_subdriver.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +return function(sub_driver_name) + -- gets the current lua libs api version + local ZwaveDriver = require "st.zwave.driver" + local version = require "version" + + if version.api >= 16 then + return ZwaveDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZwaveDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end + +end diff --git a/drivers/SmartThings/zwave-thermostat/src/popp-radiator-thermostat/can_handle.lua b/drivers/SmartThings/zwave-thermostat/src/popp-radiator-thermostat/can_handle.lua new file mode 100644 index 0000000000..19e5f39cf3 --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/popp-radiator-thermostat/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_popp_radiator_thermostat(opts, driver, device, ...) + local POPP_THERMOSTAT_FINGERPRINT = {mfr = 0x0002, prod = 0x0115, model = 0xA010} + + if device:id_match(POPP_THERMOSTAT_FINGERPRINT.mfr, POPP_THERMOSTAT_FINGERPRINT.prod, POPP_THERMOSTAT_FINGERPRINT.model) then + return true, require "popp-radiator-thermostat" + else + return false + end +end + +return can_handle_popp_radiator_thermostat diff --git a/drivers/SmartThings/zwave-thermostat/src/popp-radiator-thermostat/init.lua b/drivers/SmartThings/zwave-thermostat/src/popp-radiator-thermostat/init.lua index f6ba9955b0..46234ea2ac 100755 --- a/drivers/SmartThings/zwave-thermostat/src/popp-radiator-thermostat/init.lua +++ b/drivers/SmartThings/zwave-thermostat/src/popp-radiator-thermostat/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local utils = require "st.utils" @@ -29,8 +19,6 @@ local LATEST_WAKEUP = "latest_wakeup" local CACHED_SETPOINT = "cached_setpoint" local POPP_WAKEUP_INTERVAL = 600 --seconds -local POPP_THERMOSTAT_FINGERPRINT = {mfr = 0x0002, prod = 0x0115, model = 0xA010} - local function get_latest_wakeup_timestamp(device) return device:get_field(LATEST_WAKEUP) end @@ -48,10 +36,6 @@ local function seconds_since_latest_wakeup(device) end end -local function can_handle_popp_radiator_thermostat(opts, driver, device, ...) - return device:id_match(POPP_THERMOSTAT_FINGERPRINT.mfr, POPP_THERMOSTAT_FINGERPRINT.prod, POPP_THERMOSTAT_FINGERPRINT.model) -end - -- POPP is a sleepy device, therefore it won't accept setpoint commands rightaway. -- That's why driver waits for a device to wake up and then sends cached setpoint command. -- Driver assumes that wakeUps come in reguraly every 10 minutes. @@ -116,7 +100,7 @@ local popp_radiator_thermostat = { lifecycle_handlers = { added = added_handler }, - can_handle = can_handle_popp_radiator_thermostat + can_handle = require("popp-radiator-thermostat.can_handle"), } return popp_radiator_thermostat diff --git a/drivers/SmartThings/zwave-thermostat/src/qubino-flush-thermostat/can_handle.lua b/drivers/SmartThings/zwave-thermostat/src/qubino-flush-thermostat/can_handle.lua new file mode 100644 index 0000000000..e5a6a8474b --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/qubino-flush-thermostat/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_qubino_thermostat(opts, driver, device, ...) + local FINGERPRINTS = require("qubino-flush-thermostat.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("qubino-flush-thermostat") + end + end + return false +end + +return can_handle_qubino_thermostat diff --git a/drivers/SmartThings/zwave-thermostat/src/qubino-flush-thermostat/fingerprints.lua b/drivers/SmartThings/zwave-thermostat/src/qubino-flush-thermostat/fingerprints.lua new file mode 100644 index 0000000000..d640f7e1ce --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/qubino-flush-thermostat/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local QUBINO_FINGERPRINTS = { + {mfr = 0x0159, prod = 0x0005, model = 0x0054}, -- Qubino Flush On/Off Thermostat 2 +} + +return QUBINO_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-thermostat/src/qubino-flush-thermostat/init.lua b/drivers/SmartThings/zwave-thermostat/src/qubino-flush-thermostat/init.lua index d80e58e2da..fd9ff2aacb 100644 --- a/drivers/SmartThings/zwave-thermostat/src/qubino-flush-thermostat/init.lua +++ b/drivers/SmartThings/zwave-thermostat/src/qubino-flush-thermostat/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + --- @type st.zwave.defaults.switch local TemperatureMeasurementDefaults = require "st.zwave.defaults.temperatureMeasurement" @@ -31,9 +21,6 @@ local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ ve --- @type st.zwave.CommandClass.ThermostatOperatingState local ThermostatOperatingState = (require "st.zwave.CommandClass.ThermostatOperatingState")({version=1}) -local QUBINO_FINGERPRINTS = { - {mfr = 0x0159, prod = 0x0005, model = 0x0054}, -- Qubino Flush On/Off Thermostat 2 -} -- parameter which tells whether device is configured heat or cool thermostat mode local DEVICE_MODE_PARAMETER = 59 @@ -47,14 +34,6 @@ local CONFIGURED_MODE = "configured_mode" local COOL_MODE = "cool" local HEAT_MODE = "heat" -local function can_handle_qubino_thermostat(opts, driver, device, ...) - for _, fingerprint in ipairs(QUBINO_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end local function info_changed(self, device, event, args) local new_parameter_value @@ -137,7 +116,7 @@ local qubino_thermostat = { infoChanged = info_changed }, NAME = "qubino thermostat", - can_handle = can_handle_qubino_thermostat + can_handle = require("qubino-flush-thermostat.can_handle"), } return qubino_thermostat diff --git a/drivers/SmartThings/zwave-thermostat/src/stelpro-ki-thermostat/can_handle.lua b/drivers/SmartThings/zwave-thermostat/src/stelpro-ki-thermostat/can_handle.lua new file mode 100644 index 0000000000..65af08df62 --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/stelpro-ki-thermostat/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_stelpro_ki_thermostat(opts, driver, device, cmd, ...) + local FINGERPRINTS = require("stelpro-ki-thermostat.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require("stelpro-ki-thermostat") + end + end + + return false +end + +return can_handle_stelpro_ki_thermostat diff --git a/drivers/SmartThings/zwave-thermostat/src/stelpro-ki-thermostat/fingerprints.lua b/drivers/SmartThings/zwave-thermostat/src/stelpro-ki-thermostat/fingerprints.lua new file mode 100644 index 0000000000..00a43a739f --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/stelpro-ki-thermostat/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local STELPRO_KI_THERMOSTAT_FINGERPRINTS = { + { manufacturerId = 0x0239, productType = 0x0001, productId = 0x0001 } -- Stelpro Ki Thermostat +} + +return STELPRO_KI_THERMOSTAT_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-thermostat/src/stelpro-ki-thermostat/init.lua b/drivers/SmartThings/zwave-thermostat/src/stelpro-ki-thermostat/init.lua index 3520e35325..7b6acbe3e7 100644 --- a/drivers/SmartThings/zwave-thermostat/src/stelpro-ki-thermostat/init.lua +++ b/drivers/SmartThings/zwave-thermostat/src/stelpro-ki-thermostat/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local log = require "log" local capabilities = require "st.capabilities" @@ -21,19 +11,7 @@ local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ ve --- @type st.zwave.CommandClass.ThermostatMode local ThermostatMode = (require "st.zwave.CommandClass.ThermostatMode")({ version = 2 }) -local STELPRO_KI_THERMOSTAT_FINGERPRINTS = { - { manufacturerId = 0x0239, productType = 0x0001, productId = 0x0001 } -- Stelpro Ki Thermostat -} - -local function can_handle_stelpro_ki_thermostat(opts, driver, device, cmd, ...) - for _, fingerprint in ipairs(STELPRO_KI_THERMOSTAT_FINGERPRINTS) do - if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - return false -end local function sensor_multilevel_report_handler(self, device, cmd) if (cmd.args.sensor_type == SensorMultilevel.sensor_type.TEMPERATURE) then @@ -138,7 +116,7 @@ local stelpro_ki_thermostat = { lifecycle_handlers = { added = device_added }, - can_handle = can_handle_stelpro_ki_thermostat, + can_handle = require("stelpro-ki-thermostat.can_handle"), } return stelpro_ki_thermostat diff --git a/drivers/SmartThings/zwave-thermostat/src/sub_drivers.lua b/drivers/SmartThings/zwave-thermostat/src/sub_drivers.lua new file mode 100644 index 0000000000..dc30091b79 --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/sub_drivers.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" +local sub_drivers = { + lazy_load_if_possible("aeotec-radiator-thermostat"), + lazy_load_if_possible("popp-radiator-thermostat"), + lazy_load_if_possible("ct100-thermostat"), + lazy_load_if_possible("fibaro-heat-controller"), + lazy_load_if_possible("stelpro-ki-thermostat"), + lazy_load_if_possible("qubino-flush-thermostat"), + lazy_load_if_possible("thermostat-heating-battery"), + lazy_load_if_possible("apiv6_bugfix"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-thermostat/src/thermostat-heating-battery/can_handle.lua b/drivers/SmartThings/zwave-thermostat/src/thermostat-heating-battery/can_handle.lua new file mode 100644 index 0000000000..f02798191a --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/thermostat-heating-battery/can_handle.lua @@ -0,0 +1,15 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_thermostat_heating_battery(opts, driver, device, cmd, ...) + local DANFOSS_LC13_THERMOSTAT_FINGERPRINTS = require "thermostat-heating-battery.fingerprints" + for _, fingerprint in ipairs(DANFOSS_LC13_THERMOSTAT_FINGERPRINTS) do + if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + return true, require "thermostat-heating-battery" + end + end + + return false +end + +return can_handle_thermostat_heating_battery diff --git a/drivers/SmartThings/zwave-thermostat/src/thermostat-heating-battery/fingerprints.lua b/drivers/SmartThings/zwave-thermostat/src/thermostat-heating-battery/fingerprints.lua new file mode 100644 index 0000000000..bc32cac629 --- /dev/null +++ b/drivers/SmartThings/zwave-thermostat/src/thermostat-heating-battery/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local DANFOSS_LC13_THERMOSTAT_FINGERPRINTS = { + { manufacturerId = 0x0002, productType = 0x0005, productId = 0x0003 }, -- Danfoss LC13 Thermostat + { manufacturerId = 0x0002, productType = 0x0005, productId = 0x0004 } -- Danfoss LC13 Thermostat +} + +return DANFOSS_LC13_THERMOSTAT_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-thermostat/src/thermostat-heating-battery/init.lua b/drivers/SmartThings/zwave-thermostat/src/thermostat-heating-battery/init.lua index 4cf3c22f24..60a9a2055f 100644 --- a/drivers/SmartThings/zwave-thermostat/src/thermostat-heating-battery/init.lua +++ b/drivers/SmartThings/zwave-thermostat/src/thermostat-heating-battery/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" local utils = require "st.utils" @@ -42,11 +32,6 @@ local CLAMP = { CELSIUS_MAX = 28 } -local DANFOSS_LC13_THERMOSTAT_FINGERPRINTS = { - { manufacturerId = 0x0002, productType = 0x0005, productId = 0x0003 }, -- Danfoss LC13 Thermostat - { manufacturerId = 0x0002, productType = 0x0005, productId = 0x0004 } -- Danfoss LC13 Thermostat -} - local WEEK = {6, 0, 1, 2, 3, 4, 5} --[[ Danfoss LC13 (Living Connect) @@ -62,16 +47,6 @@ Note: https://idency.com/products/idencyhome/smarthome/sensors/danfoss-z-wave-li to the Z-Wave network, it only allows a one-way communication to change its setpoint. --]] -local function can_handle_thermostat_heating_battery(opts, driver, device, cmd, ...) - for _, fingerprint in ipairs(DANFOSS_LC13_THERMOSTAT_FINGERPRINTS) do - if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - return true - end - end - - return false -end - local function adjust_temperature_if_exceeded_min_max_limit (degree, scale) if scale == ThermostatSetpoint.scale.CELSIUS then return utils.clamp_value(degree, CLAMP.CELSIUS_MIN, CLAMP.CELSIUS_MAX) @@ -283,7 +258,7 @@ local thermostat_heating_battery = { init = device_init, added = added_handler, }, - can_handle = can_handle_thermostat_heating_battery + can_handle = require("thermostat-heating-battery.can_handle"), } return thermostat_heating_battery diff --git a/drivers/SmartThings/zwave-window-treatment/src/aeotec-nano-shutter/can_handle.lua b/drivers/SmartThings/zwave-window-treatment/src/aeotec-nano-shutter/can_handle.lua new file mode 100644 index 0000000000..7caf966d19 --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/aeotec-nano-shutter/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_aeotec_nano_shutter(opts, driver, device, ...) + local FINGERPRINTS = require("aeotec-nano-shutter.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("aeotec-nano-shutter") + end + end + return false +end + +return can_handle_aeotec_nano_shutter diff --git a/drivers/SmartThings/zwave-window-treatment/src/aeotec-nano-shutter/fingerprints.lua b/drivers/SmartThings/zwave-window-treatment/src/aeotec-nano-shutter/fingerprints.lua new file mode 100644 index 0000000000..dcbcf2f872 --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/aeotec-nano-shutter/fingerprints.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local AEOTEC_NANO_SHUTTER_FINGERPRINTS = { + {mfr = 0x0086, prod = 0x0003, model = 0x008D}, -- Aeotec nano shutter EU + {mfr = 0x0086, prod = 0x0103, model = 0x008D}, -- Aeotec nano shutter US + {mfr = 0x0371, prod = 0x0003, model = 0x008D}, -- Aeotec nano shutter EU + {mfr = 0x0371, prod = 0x0103, model = 0x008D} -- Aeotec nano shutter US +} + +return AEOTEC_NANO_SHUTTER_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-window-treatment/src/aeotec-nano-shutter/init.lua b/drivers/SmartThings/zwave-window-treatment/src/aeotec-nano-shutter/init.lua index 6ca6fd7837..82977a7cfb 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/aeotec-nano-shutter/init.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/aeotec-nano-shutter/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -28,26 +18,12 @@ local SET_BUTTON_TO_CLOSE = "close" local SET_BUTTON_TO_PAUSE = "pause" local SHADE_STATE = "shade_state" -local AEOTEC_NANO_SHUTTER_FINGERPRINTS = { - {mfr = 0x0086, prod = 0x0003, model = 0x008D}, -- Aeotec nano shutter EU - {mfr = 0x0086, prod = 0x0103, model = 0x008D}, -- Aeotec nano shutter US - {mfr = 0x0371, prod = 0x0003, model = 0x008D}, -- Aeotec nano shutter EU - {mfr = 0x0371, prod = 0x0103, model = 0x008D} -- Aeotec nano shutter US -} --- Determine whether the passed device is proper --- --- @param driver st.zwave.Driver --- @param device st.zwave.Device --- @return boolean true if the device is proper, else false -local function can_handle_aeotec_nano_shutter(opts, driver, device, ...) - for _, fingerprint in ipairs(AEOTEC_NANO_SHUTTER_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end --- Default handler for basic reports for the devices --- @@ -140,7 +116,7 @@ local aeotec_nano_shutter = { added = added_handler }, NAME = "Aeotec nano shutter", - can_handle = can_handle_aeotec_nano_shutter + can_handle = require("aeotec-nano-shutter.can_handle"), } return aeotec_nano_shutter diff --git a/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/can_handle.lua b/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/can_handle.lua new file mode 100644 index 0000000000..881ebd36a3 --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_iblinds_window_treatment(opts, driver, device, ...) + local FINGERPRINTS = require("iblinds-window-treatment.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("iblinds-window-treatment") + end + end + return false +end + +return can_handle_iblinds_window_treatment diff --git a/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/fingerprints.lua b/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/fingerprints.lua new file mode 100644 index 0000000000..d4f652f8b9 --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local IBLINDS_WINDOW_TREATMENT_FINGERPRINTS = { + {mfr = 0x0287, prod = 0x0003, model = 0x000D}, -- iBlinds Window Treatment v1 / v2 + {mfr = 0x0287, prod = 0x0004, model = 0x0071}, -- iBlinds Window Treatment v3 + {mfr = 0x0287, prod = 0x0004, model = 0x0072} -- iBlinds Window Treatment v3.1 +} + +return IBLINDS_WINDOW_TREATMENT_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/init.lua b/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/init.lua index 690e50d694..9c3d2d9970 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/init.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/init.lua @@ -1,41 +1,18 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass.SwitchMultilevel local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({ version=3 }) local window_preset_defaults = require "window_preset_defaults" -local IBLINDS_WINDOW_TREATMENT_FINGERPRINTS = { - {mfr = 0x0287, prod = 0x0003, model = 0x000D}, -- iBlinds Window Treatment v1 / v2 - {mfr = 0x0287, prod = 0x0004, model = 0x0071}, -- iBlinds Window Treatment v3 - {mfr = 0x0287, prod = 0x0004, model = 0x0072} -- iBlinds Window Treatment v3.1 -} --- Determine whether the passed device is iblinds window treatment --- --- @param driver st.zwave.Driver --- @param device st.zwave.Device --- @return boolean true if the device is iblinds window treatment, else false -local function can_handle_iblinds_window_treatment(opts, driver, device, ...) - for _, fingerprint in ipairs(IBLINDS_WINDOW_TREATMENT_FINGERPRINTS) do - if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end local capability_handlers = {} @@ -91,11 +68,9 @@ local iblinds_window_treatment = { [capabilities.windowShadePreset.commands.presetPosition.NAME] = capability_handlers.preset_position } }, - sub_drivers = { - require("iblinds-window-treatment.v3") - }, + sub_drivers = require("iblinds-window-treatment.sub_drivers"), NAME = "iBlinds window treatment", - can_handle = can_handle_iblinds_window_treatment + can_handle = require("iblinds-window-treatment.can_handle"), } return iblinds_window_treatment diff --git a/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/sub_drivers.lua b/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/sub_drivers.lua new file mode 100644 index 0000000000..187ef0ee04 --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/sub_drivers.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require("lazy_load_subdriver") + +return { + lazy_load_if_possible("iblinds-window-treatment.v3") +} diff --git a/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3/can_handle.lua b/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3/can_handle.lua new file mode 100644 index 0000000000..ababe18ac6 --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3/can_handle.lua @@ -0,0 +1,19 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +--- Determine whether the passed device is iblinds window treatment v3 +--- +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @return boolean true if the device is iblinds window treatment, else false +local function can_handle_iblinds_window_treatment_v3(opts, driver, device, ...) + local FINGERPRINTS = require("iblinds-window-treatment.v3.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("iblinds-window-treatment.v3") + end + end + return false +end + +return can_handle_iblinds_window_treatment_v3 diff --git a/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3/fingerprints.lua b/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3/fingerprints.lua new file mode 100644 index 0000000000..ee256ea18a --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local IBLINDS_WINDOW_TREATMENT_FINGERPRINTS_V3 = { + {mfr = 0x0287, prod = 0x0004, model = 0x0071}, -- iBlinds Window Treatment v3 + {mfr = 0x0287, prod = 0x0004, model = 0x0072} -- iBlinds Window Treatment v3 +} + +return IBLINDS_WINDOW_TREATMENT_FINGERPRINTS_V3 diff --git a/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3.lua b/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3/init.lua similarity index 84% rename from drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3.lua rename to drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3/init.lua index 9e0a38775d..c0bb8bc363 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass.SwitchMultilevel diff --git a/drivers/SmartThings/zwave-window-treatment/src/init.lua b/drivers/SmartThings/zwave-window-treatment/src/init.lua index aaaf5c7889..b2597d9f61 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/init.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.defaults @@ -84,12 +74,7 @@ local driver_template = { [capabilities.windowShadePreset.commands.presetPosition.NAME] = window_preset_defaults.window_shade_preset_cmd, } }, - sub_drivers = { - require("springs-window-fashion-shade"), - require("iblinds-window-treatment"), - require("window-treatment-venetian"), - require("aeotec-nano-shutter") - } + sub_drivers = require("sub_drivers"), } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) diff --git a/drivers/SmartThings/zwave-window-treatment/src/lazy_load_subdriver.lua b/drivers/SmartThings/zwave-window-treatment/src/lazy_load_subdriver.lua new file mode 100644 index 0000000000..45115081e4 --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/lazy_load_subdriver.lua @@ -0,0 +1,18 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + +return function(sub_driver_name) + -- gets the current lua libs api version + local ZwaveDriver = require "st.zwave.driver" + local version = require "version" + + if version.api >= 16 then + return ZwaveDriver.lazy_load_sub_driver_v2(sub_driver_name) + elseif version.api >= 9 then + return ZwaveDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end + +end diff --git a/drivers/SmartThings/zwave-window-treatment/src/preferences.lua b/drivers/SmartThings/zwave-window-treatment/src/preferences.lua index e8854c0bc0..54bad4c0ec 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/preferences.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/preferences.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local devices = { QUBINO = { diff --git a/drivers/SmartThings/zwave-window-treatment/src/springs-window-fashion-shade/can_handle.lua b/drivers/SmartThings/zwave-window-treatment/src/springs-window-fashion-shade/can_handle.lua new file mode 100644 index 0000000000..12d85394cf --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/springs-window-fashion-shade/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_springs_window_fashion_shade(opts, driver, device, ...) + local FINGERPRINTS = require("springs-window-fashion-shade.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match( fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("springs-window-fashion-shade") + end + end + return false +end + +return can_handle_springs_window_fashion_shade diff --git a/drivers/SmartThings/zwave-window-treatment/src/springs-window-fashion-shade/fingerprints.lua b/drivers/SmartThings/zwave-window-treatment/src/springs-window-fashion-shade/fingerprints.lua new file mode 100644 index 0000000000..7730b633e8 --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/springs-window-fashion-shade/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local SPRINGS_WINDOW_FINGERPRINTS = { + {mfr = 0x026E, prod = 0x4353, model = 0x5A31}, -- Springs Window Shade + {mfr = 0x026E, prod = 0x5253, model = 0x5A31}, -- Springs Roller Shade +} + +return SPRINGS_WINDOW_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-window-treatment/src/springs-window-fashion-shade/init.lua b/drivers/SmartThings/zwave-window-treatment/src/springs-window-fashion-shade/init.lua index e501eecdff..2f4d3c38e6 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/springs-window-fashion-shade/init.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/springs-window-fashion-shade/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.constants @@ -18,24 +8,12 @@ local constants = require "st.zwave.constants" --- @type st.zwave.CommandClass.SwitchMultilevel local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({version=4}) -local SPRINGS_WINDOW_FINGERPRINTS = { - {mfr = 0x026E, prod = 0x4353, model = 0x5A31}, -- Springs Window Shade - {mfr = 0x026E, prod = 0x5253, model = 0x5A31}, -- Springs Roller Shade -} --- Determine whether the passed device is springs window fashion shade --- --- @param driver st.zwave.Driver --- @param device st.zwave.Device --- @return boolean true if the device is springs window fashion shade, else false -local function can_handle_springs_window_fashion_shade(opts, driver, device, ...) - for _, fingerprint in ipairs(SPRINGS_WINDOW_FINGERPRINTS) do - if device:id_match( fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end local function init_handler(self, device) -- This device has a preset position set in hardware, so we need to override the base driver @@ -77,7 +55,7 @@ local springs_window_fashion_shade = { } }, NAME = "Springs window fashion shade", - can_handle = can_handle_springs_window_fashion_shade, + can_handle = require("springs-window-fashion-shade.can_handle"), } return springs_window_fashion_shade diff --git a/drivers/SmartThings/zwave-window-treatment/src/sub_drivers.lua b/drivers/SmartThings/zwave-window-treatment/src/sub_drivers.lua new file mode 100644 index 0000000000..6db8853e1a --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/sub_drivers.lua @@ -0,0 +1,11 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" +local sub_drivers = { + lazy_load_if_possible("springs-window-fashion-shade"), + lazy_load_if_possible("iblinds-window-treatment"), + lazy_load_if_possible("window-treatment-venetian"), + lazy_load_if_possible("aeotec-nano-shutter"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-window-treatment/src/test/test_fibaro_roller_shutter.lua b/drivers/SmartThings/zwave-window-treatment/src/test/test_fibaro_roller_shutter.lua index 9c879806f6..c601ba630f 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/test/test_fibaro_roller_shutter.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/test/test_fibaro_roller_shutter.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-window-treatment/src/test/test_qubino_flush_shutter.lua b/drivers/SmartThings/zwave-window-treatment/src/test/test_qubino_flush_shutter.lua index 5431ed1e70..edaffb4214 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/test/test_qubino_flush_shutter.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/test/test_qubino_flush_shutter.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_aeotec_nano_shutter.lua b/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_aeotec_nano_shutter.lua index c11b578b07..089fc29bac 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_aeotec_nano_shutter.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_aeotec_nano_shutter.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local zw = require "st.zwave" diff --git a/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_iblinds_window_treatment.lua b/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_iblinds_window_treatment.lua index f53e540fc4..e2134b1163 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_iblinds_window_treatment.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_iblinds_window_treatment.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_springs_window_treatment.lua b/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_springs_window_treatment.lua index be3b3a6405..843b26e407 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_springs_window_treatment.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_springs_window_treatment.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local constants = require "st.zwave.constants" diff --git a/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_window_treatment.lua b/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_window_treatment.lua index 534f6c56f7..fa6fd809e7 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_window_treatment.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_window_treatment.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local test = require "integration_test" local capabilities = require "st.capabilities" diff --git a/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/can_handle.lua b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/can_handle.lua new file mode 100644 index 0000000000..f2eb62ad27 --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_window_treatment_venetian(opts, driver, device, ...) + local FINGERPRINTS = require("window-treatment-venetian.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match( fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("window-treatment-venetian") + end + end + return false +end + +return can_handle_window_treatment_venetian diff --git a/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fibaro-roller-shutter/can_handle.lua b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fibaro-roller-shutter/can_handle.lua new file mode 100644 index 0000000000..cc9ca62b1c --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fibaro-roller-shutter/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_fibaro_roller_shutter(opts, driver, device, ...) + local FINGERPRINTS = require("window-treatment-venetian.fibaro-roller-shutter.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match( fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("window-treatment-venetian.fibaro-roller-shutter") + end + end + return false +end + +return can_handle_fibaro_roller_shutter diff --git a/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fibaro-roller-shutter/fingerprints.lua b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fibaro-roller-shutter/fingerprints.lua new file mode 100644 index 0000000000..925685157f --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fibaro-roller-shutter/fingerprints.lua @@ -0,0 +1,8 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local FIBARO_ROLLER_SHUTTER_FINGERPRINTS = { + {mfr = 0x010F, prod = 0x1D01, model = 0x1000}, -- Fibaro Walli Roller Shutter +} + +return FIBARO_ROLLER_SHUTTER_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fibaro-roller-shutter/init.lua b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fibaro-roller-shutter/init.lua index dbdde5b87c..5581e3a5af 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fibaro-roller-shutter/init.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fibaro-roller-shutter/init.lua @@ -1,25 +1,12 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + --- @type st.zwave.CommandClass local cc = (require "st.zwave.CommandClass") --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({version=1}) -local FIBARO_ROLLER_SHUTTER_FINGERPRINTS = { - {mfr = 0x010F, prod = 0x1D01, model = 0x1000}, -- Fibaro Walli Roller Shutter -} -- configuration parameters local CALIBRATION_CONFIGURATION = 150 @@ -33,14 +20,6 @@ local CLB_NOT_STARTED = "not_started" local CLB_DONE = "done" local CLB_PENDING = "pending" -local function can_handle_fibaro_roller_shutter(opts, driver, device, ...) - for _, fingerprint in ipairs(FIBARO_ROLLER_SHUTTER_FINGERPRINTS) do - if device:id_match( fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end local function configuration_report(driver, device, cmd) local parameter_number = cmd.args.parameter_number @@ -79,7 +58,7 @@ local fibaro_roller_shutter = { } }, NAME = "fibaro roller shutter", - can_handle = can_handle_fibaro_roller_shutter, + can_handle = require("window-treatment-venetian.fibaro-roller-shutter.can_handle"), lifecycle_handlers = { add = device_added } diff --git a/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fingerprints.lua b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fingerprints.lua new file mode 100644 index 0000000000..91dc96c5b8 --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/fingerprints.lua @@ -0,0 +1,10 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local WINDOW_TREATMENT_VENETIAN_FINGERPRINTS = { + {mfr = 0x010F, prod = 0x1D01, model = 0x1000}, -- Fibaro Walli Roller Shutter + {mfr = 0x0159, prod = 0x0003, model = 0x0052}, -- Qubino Flush Shutter AC + {mfr = 0x0159, prod = 0x0003, model = 0x0053}, -- Qubino Flush Shutter DC +} + +return WINDOW_TREATMENT_VENETIAN_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/init.lua b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/init.lua index 2ff27250a8..c201ed32eb 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/init.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/init.lua @@ -1,16 +1,7 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + + local cc = (require "st.zwave.CommandClass") local Basic = (require "st.zwave.CommandClass.Basic")({ version=1 }) @@ -19,20 +10,7 @@ local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({ver local WindowShadeDefaults = require "st.zwave.defaults.windowShade" local WindowShadeLevelDefaults = require "st.zwave.defaults.windowShadeLevel" -local WINDOW_TREATMENT_VENETIAN_FINGERPRINTS = { - {mfr = 0x010F, prod = 0x1D01, model = 0x1000}, -- Fibaro Walli Roller Shutter - {mfr = 0x0159, prod = 0x0003, model = 0x0052}, -- Qubino Flush Shutter AC - {mfr = 0x0159, prod = 0x0003, model = 0x0053}, -- Qubino Flush Shutter DC -} -local function can_handle_window_treatment_venetian(opts, driver, device, ...) - for _, fingerprint in ipairs(WINDOW_TREATMENT_VENETIAN_FINGERPRINTS) do - if device:id_match( fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end local function shade_event_handler(self, device, cmd) WindowShadeDefaults.zwave_handlers[cc.SWITCH_MULTILEVEL][SwitchMultilevel.REPORT](self, device, cmd) @@ -70,14 +48,11 @@ local window_treatment_venetian = { [SwitchMultilevel.REPORT] = shade_event_handler } }, - can_handle = can_handle_window_treatment_venetian, + can_handle = require("window-treatment-venetian.can_handle"), lifecycle_handlers = { init = map_components }, - sub_drivers = { - require("window-treatment-venetian/fibaro-roller-shutter"), - require("window-treatment-venetian/qubino-flush-shutter") - } + sub_drivers = require("window-treatment-venetian.sub_drivers"), } return window_treatment_venetian diff --git a/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/qubino-flush-shutter/can_handle.lua b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/qubino-flush-shutter/can_handle.lua new file mode 100644 index 0000000000..e601800084 --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/qubino-flush-shutter/can_handle.lua @@ -0,0 +1,14 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_qubino_flush_shutter(opts, self, device, ...) + local FINGERPRINTS = require("window-treatment-venetian.qubino-flush-shutter.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:id_match( fingerprint.mfr, fingerprint.prod, fingerprint.model) then + return true, require("window-treatment-venetian.qubino-flush-shutter") + end + end + return false +end + +return can_handle_qubino_flush_shutter diff --git a/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/qubino-flush-shutter/fingerprints.lua b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/qubino-flush-shutter/fingerprints.lua new file mode 100644 index 0000000000..098879f313 --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/qubino-flush-shutter/fingerprints.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local QUBINO_FLUSH_SHUTTER_FINGERPRINTS = { + {mfr = 0x0159, prod = 0x0003, model = 0x0052}, -- Qubino Flush Shutter AC + {mfr = 0x0159, prod = 0x0003, model = 0x0053}, -- Qubino Flush Shutter DC +} + +return QUBINO_FLUSH_SHUTTER_FINGERPRINTS diff --git a/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/qubino-flush-shutter/init.lua b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/qubino-flush-shutter/init.lua index fafc6d5026..56ba50f516 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/qubino-flush-shutter/init.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/qubino-flush-shutter/init.lua @@ -1,16 +1,6 @@ --- Copyright 2022 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. +-- Copyright 2022 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass @@ -41,19 +31,7 @@ local SHADE_TARGET = "shade_target" local ENERGY_UNIT_KWH = "kWh" local POWER_UNIT_WATT = "W" -local QUBINO_FLUSH_SHUTTER_FINGERPRINTS = { - {mfr = 0x0159, prod = 0x0003, model = 0x0052}, -- Qubino Flush Shutter AC - {mfr = 0x0159, prod = 0x0003, model = 0x0053}, -- Qubino Flush Shutter DC -} -local function can_handle_qubino_flush_shutter(opts, self, device, ...) - for _, fingerprint in ipairs(QUBINO_FLUSH_SHUTTER_FINGERPRINTS) do - if device:id_match( fingerprint.mfr, fingerprint.prod, fingerprint.model) then - return true - end - end - return false -end local function configuration_report(self, device, cmd) local parameter_number = cmd.args.parameter_number @@ -190,7 +168,7 @@ local qubino_flush_shutter = { [capabilities.windowShade.commands.close.NAME] = close }, }, - can_handle = can_handle_qubino_flush_shutter, + can_handle = require("window-treatment-venetian.qubino-flush-shutter.can_handle"), lifecycle_handlers = { added = device_added, infoChanged = info_changed diff --git a/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/sub_drivers.lua b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/sub_drivers.lua new file mode 100644 index 0000000000..41600cffef --- /dev/null +++ b/drivers/SmartThings/zwave-window-treatment/src/window-treatment-venetian/sub_drivers.lua @@ -0,0 +1,9 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_load_if_possible = require "lazy_load_subdriver" +local sub_drivers = { + lazy_load_if_possible("window-treatment-venetian/fibaro-roller-shutter"), + lazy_load_if_possible("window-treatment-venetian/qubino-flush-shutter"), +} +return sub_drivers diff --git a/drivers/SmartThings/zwave-window-treatment/src/window_preset_defaults.lua b/drivers/SmartThings/zwave-window-treatment/src/window_preset_defaults.lua index 5046940dea..3d5056abc5 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/window_preset_defaults.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/window_preset_defaults.lua @@ -1,16 +1,6 @@ --- Copyright 2025 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. +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + -- These were added to scripting engine, but this file is to make sure drivers @@ -62,4 +52,4 @@ defaults.window_shade_preset_cmd = function(driver, device, command) device.thread:call_with_delay(constants.MIN_DIMMING_GET_STATUS_DELAY, query_device) end -return defaults \ No newline at end of file +return defaults diff --git a/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_curtain.lua b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_curtain.lua index 7a0ea0b215..ca09608347 100644 --- a/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_curtain.lua +++ b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_curtain.lua @@ -145,7 +145,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x01', tuya_utils.DP_TYPE_ENUM, '\x00', 2) } + message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x01', tuya_utils.DP_TYPE_ENUM, '\x00', 0) } }, { channel = "capability", @@ -166,7 +166,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x01', tuya_utils.DP_TYPE_ENUM, '\x02', 3) } + message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x01', tuya_utils.DP_TYPE_ENUM, '\x02', 0) } }, { channel = "capability", @@ -187,7 +187,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x01', tuya_utils.DP_TYPE_ENUM, '\x01', 4) } + message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x01', tuya_utils.DP_TYPE_ENUM, '\x01', 0) } } } ) @@ -203,7 +203,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x3c', 5) } + message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x3c', 0) } } } ) @@ -219,7 +219,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x32', 6) } + message = { mock_simple_device.id, tuya_utils.build_send_tuya_command(mock_simple_device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00\x00\x32', 0) } } } ) diff --git a/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_switch.lua b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_switch.lua index 4da9d411d7..97537c36ad 100644 --- a/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_switch.lua +++ b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_switch.lua @@ -252,7 +252,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x02', tuya_utils.DP_TYPE_BOOL, '\x01', 1) } + message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x02', tuya_utils.DP_TYPE_BOOL, '\x01', 0) } } } ) @@ -268,7 +268,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x03', tuya_utils.DP_TYPE_BOOL, '\x01', 2) } + message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x03', tuya_utils.DP_TYPE_BOOL, '\x01', 0) } } } ) @@ -284,7 +284,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x04', tuya_utils.DP_TYPE_BOOL, '\x01', 3) } + message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x04', tuya_utils.DP_TYPE_BOOL, '\x01', 0) } } } ) @@ -300,7 +300,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x05', tuya_utils.DP_TYPE_BOOL, '\x01', 4) } + message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x05', tuya_utils.DP_TYPE_BOOL, '\x01', 0) } } } ) @@ -316,7 +316,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x06', tuya_utils.DP_TYPE_BOOL, '\x01', 5) } + message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x06', tuya_utils.DP_TYPE_BOOL, '\x01', 0) } } } ) @@ -332,7 +332,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x01', tuya_utils.DP_TYPE_BOOL, '\x00', 6) } + message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x01', tuya_utils.DP_TYPE_BOOL, '\x00', 0) } } } ) @@ -348,7 +348,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x02', tuya_utils.DP_TYPE_BOOL, '\x00', 7) } + message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x02', tuya_utils.DP_TYPE_BOOL, '\x00', 0) } } } ) @@ -364,7 +364,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x03', tuya_utils.DP_TYPE_BOOL, '\x00', 8) } + message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x03', tuya_utils.DP_TYPE_BOOL, '\x00', 0) } } } ) @@ -380,7 +380,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x04', tuya_utils.DP_TYPE_BOOL, '\x00', 9) } + message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x04', tuya_utils.DP_TYPE_BOOL, '\x00', 0) } } } ) @@ -396,7 +396,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x05', tuya_utils.DP_TYPE_BOOL, '\x00', 10) } + message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x05', tuya_utils.DP_TYPE_BOOL, '\x00', 0) } } } ) @@ -412,7 +412,7 @@ test.register_message_test( { channel = "zigbee", direction = "send", - message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x06', tuya_utils.DP_TYPE_BOOL, '\x00', 11) } + message = { mock_parent_device.id, tuya_utils.build_send_tuya_command(mock_parent_device, '\x06', tuya_utils.DP_TYPE_BOOL, '\x00', 0) } } } ) diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index d9105f0949..952b4a5c44 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -116,4 +116,18 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WISTAR WSERD50-L Smart Tubular Motor",威仕达智能管状电机 WSERD50-L "WISTAR WSERD50-T Smart Tubular Motor",威仕达智能管状电机 WSERD50-T "WISTAR WSER60 Smart Tubular Motor",威仕达智能管状电机 WSER60 +"WISTAR WSCMXH Smart Vertical Blind Motor",威仕达智能梦幻帘电机 WSCMXH +"WISTAR WSCMXF Smart Vertical Blind Motor",威仕达智能梦幻帘电机 WSCMXF +"WISTAR WSCMXF-LED Smart Vertical Blind Motor",威仕达智能梦幻帘电机 WSCMXF-LED "VIVIDSTORM Smart Screen VWSDSTUST120H",VIVIDSTORM智能幕布 VWSDSTUST120H +"HOPOsmart Window Opener A2230011",HOPOsmart链式开窗器 A2230011 +"Yanmi Switch (3 Way)",岩米三位智能开关面板 +"Onvis Smart Plug S4EU",Onvis 智能插座S4EU +"Yanmi Switch (2 Way)",岩米二位智能开关面板 +"Yanmi Switch (1 Way)",岩米一位智能开关面板 +"WISTAR WSCMQ Smart Curtain Motor",威仕达智能开合帘电机 WSCMQ +"WISTAR WSCMXI Smart Curtain Motor",威仕达智能开合帘电机 WSCMXI +"WISTAR WSCMT Smart Curtain Motor",威仕达智能开合帘电机 WSCMT +"WISTAR WSCMXB Smart Curtain Motor",威仕达智能开合帘电机 WSCMXB +"WISTAR WSCMXC Smart Curtain Motor",威仕达智能开合帘电机 WSCMXC +"WISTAR WSCMXJ Smart Curtain Motor",威仕达智能开合帘电机 WSCMXJ diff --git a/tools/pre-commit b/tools/pre-commit new file mode 100755 index 0000000000..c9bc4d4d4f --- /dev/null +++ b/tools/pre-commit @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# +# Copyright 2025 SmartThings, Inc. +# Licensed under the Apache License, Version 2.0 +# +# pre-commit +# +# - Soft link into .git/hooks/pre-commit +# - $ ln -s ../../tools/pre-commit .git/hooks/pre-commit +# - Ensure executable + +set -e + +# Get changed files that are Added or Modified +if [[ "$1" ]]; then + files=$(find $1 -type f -follow -print) +else + files=$(git diff --staged --name-only --diff-filter=AM) +fi + +fail=0 +for file in $files; do + # Only process .lua files + case "$file" in + *.lua) + # Skip if not a regular file + [ -f "$file" ] || continue + + # Check if file is not empty, is a SmartThings driver and has the copyright header + if [ -s "$file" ] && [[ "$file" =~ "drivers/SmartThings" ]] \ + && [[ ! $(grep $file -P -e "-{2,} Copyright \d{4}(?:-\d{4})? SmartThings") ]]; then + echo "$file: SmartThings Copyright missing from file" + fail=1 + fi + + # Check if file is non-empty and does NOT end with a newline + if [ -s "$file" ] && [ -n "$(tail -c 1 "$file")" ]; then + echo "$file: Missing newline at end of file" + fail=1 + fi + ;; + esac +done + +exit $fail diff --git a/tools/run_driver_tests.py b/tools/run_driver_tests.py index c797ef5320..e3e58f2154 100755 --- a/tools/run_driver_tests.py +++ b/tools/run_driver_tests.py @@ -7,6 +7,7 @@ import argparse from pathlib import Path import junit_xml +import shutil VERBOSITY_TOTALS_ONLY = 0 VERBOSITY_TEST_STATUS_ONLY = 1 @@ -14,7 +15,7 @@ VERBOSITY_ALL_TEST_LOGS = 3 DRIVER_DIR = Path(os.path.abspath(__file__)).parents[1].joinpath("drivers") -LUACOV_CONFIG = DRIVER_DIR.parent.joinpath(".circleci", "config.luacov") +LUACOV_CONFIG = DRIVER_DIR.parent.joinpath("tools", "config.luacov") def find_affected_tests(working_dir, changed_files): affected_tests = [] @@ -29,13 +30,14 @@ def find_affected_tests(working_dir, changed_files): affected_tests = set(affected_tests) return affected_tests -def run_tests(verbosity_level, filter, junit, coverage_files): +def run_tests(verbosity_level, filter, junit, coverage_files, html): owd = os.getcwd() coverage_files = find_affected_tests(owd, coverage_files) failure_files = defaultdict(list) ts = [] total_tests = 0 total_passes = 0 + drivers_needing_html = {} for test_file in DRIVER_DIR.glob("*" + os.path.sep + "*" + os.path.sep + "src" + os.path.sep + "test" + os.path.sep + "test_*.lua"): if filter != None and re.search(filter, str(test_file)) is None: continue @@ -137,7 +139,29 @@ def run_tests(verbosity_level, filter, junit, coverage_files): test_suite.test_cases = test_cases ts.append(test_suite) if test_file in coverage_files: - subprocess.run("luacov -c={}".format(LUACOV_CONFIG), shell=True) + if html: + driver_name = test_file.parts[-4] + src_path = test_file.parents[1] + drivers_needing_html[driver_name] = src_path + else: + subprocess.run("luacov -c={}".format(LUACOV_CONFIG), shell=True) + + if drivers_needing_html: + coverage_html_dir = DRIVER_DIR.parent.joinpath("tools/coverage_output_html") + try: + os.mkdir(coverage_html_dir) + except FileExistsError: + pass + + for driver_name, src_path in drivers_needing_html.items(): + os.chdir(src_path) + subprocess.run("luacov -c {} --reporter html".format(LUACOV_CONFIG), shell=True) + html_source = Path("luacov.report.out") + html_dest = coverage_html_dir.joinpath(driver_name+"_luacov.report.html") + if html_source.exists(): + shutil.copy(html_source, html_dest) + else: + print(f"Warning: HTML coverage file for {driver_name} not found") total_test_info = "Total unit tests passes: {}/{}".format(total_passes, total_tests) print("#" * len(total_test_info)) @@ -165,6 +189,7 @@ def run_tests(verbosity_level, filter, junit, coverage_files): parser.add_argument("--filter", "-f", type=str, nargs="?", help="only run tests containing the filter value in the path") parser.add_argument("--junit", "-j", type=str, nargs="?", help="output test results in JUnit XML to the specified file") parser.add_argument("--coverage", "-c", nargs="*", help="run code tests with coverage (luacov must be installed) OPTIONAL: restrict files to run coverage tests for") + parser.add_argument("--html", action="store_true", help="Generate HTML coverage reports for the files specified by the coverage argument") args = parser.parse_args() verbosity_level = 0 if args.verbose: @@ -173,5 +198,4 @@ def run_tests(verbosity_level, filter, junit, coverage_files): verbosity_level = 2 elif args.superextraverbose: verbosity_level = 3 - run_tests(verbosity_level, args.filter, args.junit, args.coverage) - + run_tests(verbosity_level, args.filter, args.junit, args.coverage, args.html) From 947153f023cb399d0326bb037b73835090571117 Mon Sep 17 00:00:00 2001 From: mh-zwave Date: Mon, 30 Mar 2026 11:54:39 +0200 Subject: [PATCH 07/10] Aeotec new devices init --- .../zwave-sensor/profiles/aeotec-aerq-8.yml | 38 +- .../profiles/aeotec-door-window-sensor-8.yml | 48 +- .../profiles/aeotec-water-sensor-8-co.yml | 36 +- .../profiles/aeotec-water-sensor-8-co2.yml | 36 +- .../aeotec-water-sensor-8-contact.yml | 34 +- .../aeotec-water-sensor-8-glass-break.yml | 34 +- .../profiles/aeotec-water-sensor-8-motion.yml | 34 +- .../profiles/aeotec-water-sensor-8-panic.yml | 34 +- .../profiles/aeotec-water-sensor-8-smoke.yml | 32 +- .../profiles/aeotec-water-sensor-8.yml | 34 +- .../zwave-sensor/src/aeotec-aerq-8/init.lua | 25 +- .../src/aeotec-door-window-sensor-8/init.lua | 42 +- .../src/aeotec-water-sensor-8/init.lua | 4 +- .../zwave-sensor/src/preferences.lua | 4 +- .../src/test/test_aeotec_aerq_8.lua | 16 +- .../test/test_aeotec_door_window_sesnor_8.lua | 573 +++++++++--------- .../src/test/test_aeotec_water_sensor_8.lua | 41 +- .../src/timed-tamper-clear/init.lua | 14 +- 18 files changed, 657 insertions(+), 422 deletions(-) diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-aerq-8.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-aerq-8.yml index 9c3ad7ff0e..7055dd8300 100644 --- a/drivers/SmartThings/zwave-sensor/profiles/aeotec-aerq-8.yml +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-aerq-8.yml @@ -4,20 +4,36 @@ components: capabilities: - id: temperatureMeasurement config: - values: - - key: "temperature.value" - range: [-10, 60] + values: + - key: "temperature.value" + range: [-10, 60] version: 1 - id: relativeHumidityMeasurement version: 1 - id: dewPoint version: 1 + config: + values: + - key: "dewpoint.value" + range: [-10, 60] - id: moldHealthConcern + config: + values: + - key: "moldHealthConcern.value" + enabledValues: + - good + - moderate version: 1 - id: tamperAlert version: 1 - id: powerSource version: 1 + config: + values: + - key: "powerSource.value" + enabledValues: + - battery + - dc - id: battery version: 1 - id: refresh @@ -32,7 +48,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 900 - name: "parameter2" title: "2 Min. temperature change to report" @@ -40,7 +56,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 20 - name: "parameter3" title: "3 Min. humidity change to report" @@ -48,7 +64,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 50 - name: "parameter4" title: "4 Enable led indication" @@ -67,7 +83,7 @@ preferences: preferenceType: integer definition: minimum: -10 - maximum : 10 + maximum: 10 default: 0 - name: "parameter23" title: "23 Low battery threshold" @@ -76,7 +92,7 @@ preferences: preferenceType: integer definition: minimum: 10 - maximum : 50 + maximum: 50 default: 20 - name: "parameter24" title: "24 Periodic Reports" @@ -85,7 +101,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 43200 - name: "parameter25" title: "25 Offset value for temperature" @@ -94,7 +110,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter26" title: "26 Offset value for Humidity" @@ -103,7 +119,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter64" title: "64 Temperature Scale" diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml index 03aac15343..6b7206293c 100644 --- a/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml @@ -5,27 +5,37 @@ components: - id: contactSensor version: 1 - id: temperatureMeasurement + version: 1 config: values: - key: "temperature.value" range: [-10, 60] - version: 1 - id: relativeHumidityMeasurement version: 1 - id: dewPoint version: 1 + config: + values: + - key: "dewpoint.value" + range: [-10, 60] - id: moldHealthConcern version: 1 + config: + values: + - key: "moldHealthConcern.value" + enabledValues: + - good + - moderate - id: tamperAlert version: 1 - id: powerSource - config: - values: - - key: "powerSource.value" - enabledValues: - - battery - - dc version: 1 + config: + values: + - key: "powerSource.value" + enabledValues: + - battery + - dc - id: threeAxis version: 1 - id: battery @@ -46,7 +56,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 900 - name: "parameter2" title: "2 Min. temperature change to report" @@ -54,7 +64,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 20 - name: "parameter3" title: "3 Min. humidity change to report" @@ -62,7 +72,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 50 - name: "parameter5" title: "5 State when the magnet is close" @@ -81,7 +91,7 @@ preferences: preferenceType: integer definition: minimum: -10 - maximum : 10 + maximum: 10 default: 0 - name: "parameter23" title: "23 Low battery threshold" @@ -90,7 +100,7 @@ preferences: preferenceType: integer definition: minimum: 10 - maximum : 50 + maximum: 50 default: 20 - name: "parameter24" title: "24 Periodic Reports" @@ -99,7 +109,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 43200 - name: "parameter25" title: "25 Offset value for temperature" @@ -108,7 +118,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter26" title: "26 Offset value for Humidity" @@ -117,7 +127,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter27" title: "27 Set tilt sensor mode" @@ -147,7 +157,7 @@ preferences: preferenceType: integer definition: minimum: 1 - maximum : 90 + maximum: 90 default: 5 - name: "parameter34" title: "34 Timeout tilt detection Mode 1" @@ -156,7 +166,7 @@ preferences: preferenceType: integer definition: minimum: 5 - maximum : 60 + maximum: 60 default: 5 - name: "parameter35" title: "35 Timeout tilt detection Mode 2" @@ -165,7 +175,7 @@ preferences: preferenceType: integer definition: minimum: 5 - maximum : 60 + maximum: 60 default: 8 - name: "parameter36" title: "36 Min. acc. change to report" @@ -174,7 +184,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 0 - name: "parameter64" title: "64 Temperature Scale" diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co.yml index 38682e8c96..c13d2254fa 100644 --- a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co.yml +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co.yml @@ -5,27 +5,43 @@ components: - id: carbonMonoxideDetector version: 1 - id: temperatureMeasurement + version: 1 config: values: - key: "temperature.value" range: [-10, 60] - version: 1 - id: relativeHumidityMeasurement version: 1 - id: dewPoint version: 1 + config: + values: + - key: "dewpoint.value" + range: [-10, 60] - id: moldHealthConcern version: 1 + config: + values: + - key: "moldHealthConcern.value" + enabledValues: + - good + - moderate - id: tamperAlert version: 1 - id: powerSource version: 1 + config: + values: + - key: "powerSource.value" + enabledValues: + - battery + - dc - id: battery version: 1 - id: refresh version: 1 categories: - - name: SmokeDetector + - name: AirQualityDetector preferences: - name: "parameter1" title: "1 Set threshold Check Time" @@ -34,7 +50,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 900 - name: "parameter2" title: "2 Min. temperature change to report" @@ -42,7 +58,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 20 - name: "parameter3" title: "3 Min. humidity change to report" @@ -50,7 +66,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 50 - name: "parameter4" title: "4 Enable led indication" @@ -96,7 +112,7 @@ preferences: preferenceType: integer definition: minimum: -10 - maximum : 10 + maximum: 10 default: 0 - name: "parameter23" title: "23 Low battery threshold" @@ -105,7 +121,7 @@ preferences: preferenceType: integer definition: minimum: 10 - maximum : 50 + maximum: 50 default: 20 - name: "parameter24" title: "24 Periodic Reports" @@ -114,7 +130,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 43200 - name: "parameter25" title: "25 Offset value for temperature" @@ -123,7 +139,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter26" title: "26 Offset value for Humidity" @@ -132,7 +148,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter64" title: "64 Temperature Scale" diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co2.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co2.yml index a048d5bd8e..36367534a6 100644 --- a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co2.yml +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-co2.yml @@ -5,27 +5,43 @@ components: - id: carbonDioxideHealthConcern version: 1 - id: temperatureMeasurement + version: 1 config: values: - key: "temperature.value" range: [-10, 60] - version: 1 - id: relativeHumidityMeasurement version: 1 - id: dewPoint version: 1 + config: + values: + - key: "dewpoint.value" + range: [-10, 60] - id: moldHealthConcern version: 1 + config: + values: + - key: "moldHealthConcern.value" + enabledValues: + - good + - moderate - id: tamperAlert version: 1 - id: powerSource version: 1 + config: + values: + - key: "powerSource.value" + enabledValues: + - battery + - dc - id: battery version: 1 - id: refresh version: 1 categories: - - name: SmokeDetector + - name: AirQualityDetector preferences: - name: "parameter1" title: "1 Set threshold Check Time" @@ -34,7 +50,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 900 - name: "parameter2" title: "2 Min. temperature change to report" @@ -42,7 +58,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 20 - name: "parameter3" title: "3 Min. humidity change to report" @@ -50,7 +66,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 50 - name: "parameter4" title: "4 Enable led indication" @@ -96,7 +112,7 @@ preferences: preferenceType: integer definition: minimum: -10 - maximum : 10 + maximum: 10 default: 0 - name: "parameter23" title: "23 Low battery threshold" @@ -105,7 +121,7 @@ preferences: preferenceType: integer definition: minimum: 10 - maximum : 50 + maximum: 50 default: 20 - name: "parameter24" title: "24 Periodic Reports" @@ -114,7 +130,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 43200 - name: "parameter25" title: "25 Offset value for temperature" @@ -123,7 +139,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter26" title: "26 Offset value for Humidity" @@ -132,7 +148,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter64" title: "64 Temperature Scale" diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-contact.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-contact.yml index eb476d9d9c..994cc7b4bd 100644 --- a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-contact.yml +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-contact.yml @@ -5,21 +5,37 @@ components: - id: contactSensor version: 1 - id: temperatureMeasurement + version: 1 config: values: - key: "temperature.value" range: [-10, 60] - version: 1 - id: relativeHumidityMeasurement version: 1 - id: dewPoint version: 1 + config: + values: + - key: "dewpoint.value" + range: [-10, 60] - id: moldHealthConcern version: 1 + config: + values: + - key: "moldHealthConcern.value" + enabledValues: + - good + - moderate - id: tamperAlert version: 1 - id: powerSource version: 1 + config: + values: + - key: "powerSource.value" + enabledValues: + - battery + - dc - id: battery version: 1 - id: refresh @@ -34,7 +50,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 900 - name: "parameter2" title: "2 Min. temperature change to report" @@ -42,7 +58,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 20 - name: "parameter3" title: "3 Min. humidity change to report" @@ -50,7 +66,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 50 - name: "parameter4" title: "4 Enable led indication" @@ -96,7 +112,7 @@ preferences: preferenceType: integer definition: minimum: -10 - maximum : 10 + maximum: 10 default: 0 - name: "parameter23" title: "23 Low battery threshold" @@ -105,7 +121,7 @@ preferences: preferenceType: integer definition: minimum: 10 - maximum : 50 + maximum: 50 default: 20 - name: "parameter24" title: "24 Periodic Reports" @@ -114,7 +130,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 43200 - name: "parameter25" title: "25 Offset value for temperature" @@ -123,7 +139,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter26" title: "26 Offset value for Humidity" @@ -132,7 +148,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter64" title: "64 Temperature Scale" diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-glass-break.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-glass-break.yml index 287efe5bba..759d0d0f94 100644 --- a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-glass-break.yml +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-glass-break.yml @@ -5,21 +5,37 @@ components: - id: soundDetection version: 1 - id: temperatureMeasurement + version: 1 config: values: - key: "temperature.value" range: [-10, 60] - version: 1 - id: relativeHumidityMeasurement version: 1 - id: dewPoint version: 1 + config: + values: + - key: "dewpoint.value" + range: [-10, 60] - id: moldHealthConcern version: 1 + config: + values: + - key: "moldHealthConcern.value" + enabledValues: + - good + - moderate - id: tamperAlert version: 1 - id: powerSource version: 1 + config: + values: + - key: "powerSource.value" + enabledValues: + - battery + - dc - id: battery version: 1 - id: refresh @@ -34,7 +50,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 900 - name: "parameter2" title: "2 Min. temperature change to report" @@ -42,7 +58,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 20 - name: "parameter3" title: "3 Min. humidity change to report" @@ -50,7 +66,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 50 - name: "parameter4" title: "4 Enable led indication" @@ -96,7 +112,7 @@ preferences: preferenceType: integer definition: minimum: -10 - maximum : 10 + maximum: 10 default: 0 - name: "parameter23" title: "23 Low battery threshold" @@ -105,7 +121,7 @@ preferences: preferenceType: integer definition: minimum: 10 - maximum : 50 + maximum: 50 default: 20 - name: "parameter24" title: "24 Periodic Reports" @@ -114,7 +130,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 43200 - name: "parameter25" title: "25 Offset value for temperature" @@ -123,7 +139,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter26" title: "26 Offset value for Humidity" @@ -132,7 +148,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter64" title: "64 Temperature Scale" diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-motion.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-motion.yml index d9f2e6ba8b..576253e2cb 100644 --- a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-motion.yml +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-motion.yml @@ -5,21 +5,37 @@ components: - id: motionSensor version: 1 - id: temperatureMeasurement + version: 1 config: values: - key: "temperature.value" range: [-10, 60] - version: 1 - id: relativeHumidityMeasurement version: 1 - id: dewPoint version: 1 + config: + values: + - key: "dewpoint.value" + range: [-10, 60] - id: moldHealthConcern version: 1 + config: + values: + - key: "moldHealthConcern.value" + enabledValues: + - good + - moderate - id: tamperAlert version: 1 - id: powerSource version: 1 + config: + values: + - key: "powerSource.value" + enabledValues: + - battery + - dc - id: battery version: 1 - id: refresh @@ -34,7 +50,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 900 - name: "parameter2" title: "2 Min. temperature change to report" @@ -42,7 +58,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 20 - name: "parameter3" title: "3 Min. humidity change to report" @@ -50,7 +66,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 50 - name: "parameter4" title: "4 Enable led indication" @@ -96,7 +112,7 @@ preferences: preferenceType: integer definition: minimum: -10 - maximum : 10 + maximum: 10 default: 0 - name: "parameter23" title: "23 Low battery threshold" @@ -105,7 +121,7 @@ preferences: preferenceType: integer definition: minimum: 10 - maximum : 50 + maximum: 50 default: 20 - name: "parameter24" title: "24 Periodic Reports" @@ -114,7 +130,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 43200 - name: "parameter25" title: "25 Offset value for temperature" @@ -123,7 +139,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter26" title: "26 Offset value for Humidity" @@ -132,7 +148,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter64" title: "64 Temperature Scale" diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-panic.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-panic.yml index d366cee6b7..3aa63e911c 100644 --- a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-panic.yml +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-panic.yml @@ -5,21 +5,37 @@ components: - id: panicAlarm version: 1 - id: temperatureMeasurement + version: 1 config: values: - key: "temperature.value" range: [-10, 60] - version: 1 - id: relativeHumidityMeasurement version: 1 - id: dewPoint version: 1 + config: + values: + - key: "dewpoint.value" + range: [-10, 60] - id: moldHealthConcern version: 1 + config: + values: + - key: "moldHealthConcern.value" + enabledValues: + - good + - moderate - id: tamperAlert version: 1 - id: powerSource version: 1 + config: + values: + - key: "powerSource.value" + enabledValues: + - battery + - dc - id: battery version: 1 - id: refresh @@ -34,7 +50,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 900 - name: "parameter2" title: "2 Min. temperature change to report" @@ -42,7 +58,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 20 - name: "parameter3" title: "3 Min. humidity change to report" @@ -50,7 +66,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 50 - name: "parameter4" title: "4 Enable led indication" @@ -96,7 +112,7 @@ preferences: preferenceType: integer definition: minimum: -10 - maximum : 10 + maximum: 10 default: 0 - name: "parameter23" title: "23 Low battery threshold" @@ -105,7 +121,7 @@ preferences: preferenceType: integer definition: minimum: 10 - maximum : 50 + maximum: 50 default: 20 - name: "parameter24" title: "24 Periodic Reports" @@ -114,7 +130,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 43200 - name: "parameter25" title: "25 Offset value for temperature" @@ -123,7 +139,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter26" title: "26 Offset value for Humidity" @@ -132,7 +148,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter64" title: "64 Temperature Scale" diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-smoke.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-smoke.yml index 06023a4962..a6686b6b57 100644 --- a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-smoke.yml +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8-smoke.yml @@ -14,12 +14,28 @@ components: version: 1 - id: dewPoint version: 1 + config: + values: + - key: "dewpoint.value" + range: [-10, 60] - id: moldHealthConcern version: 1 + config: + values: + - key: "moldHealthConcern.value" + enabledValues: + - good + - moderate - id: tamperAlert version: 1 - id: powerSource version: 1 + config: + values: + - key: "powerSource.value" + enabledValues: + - battery + - dc - id: battery version: 1 - id: refresh @@ -34,7 +50,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 900 - name: "parameter2" title: "2 Min. temperature change to report" @@ -42,7 +58,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 20 - name: "parameter3" title: "3 Min. humidity change to report" @@ -50,7 +66,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 50 - name: "parameter4" title: "4 Enable led indication" @@ -96,7 +112,7 @@ preferences: preferenceType: integer definition: minimum: -10 - maximum : 10 + maximum: 10 default: 0 - name: "parameter23" title: "23 Low battery threshold" @@ -105,7 +121,7 @@ preferences: preferenceType: integer definition: minimum: 10 - maximum : 50 + maximum: 50 default: 20 - name: "parameter24" title: "24 Periodic Reports" @@ -114,7 +130,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 43200 - name: "parameter25" title: "25 Offset value for temperature" @@ -123,7 +139,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter26" title: "26 Offset value for Humidity" @@ -132,7 +148,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter64" title: "64 Temperature Scale" diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8.yml index 0e27580c9f..717b2c8ce0 100644 --- a/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8.yml +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-water-sensor-8.yml @@ -5,21 +5,37 @@ components: - id: waterSensor version: 1 - id: temperatureMeasurement + version: 1 config: values: - key: "temperature.value" range: [-10, 60] - version: 1 - id: relativeHumidityMeasurement version: 1 - id: dewPoint version: 1 + config: + values: + - key: "dewpoint.value" + range: [-10, 60] - id: moldHealthConcern version: 1 + config: + values: + - key: "moldHealthConcern.value" + enabledValues: + - good + - moderate - id: tamperAlert version: 1 - id: powerSource version: 1 + config: + values: + - key: "powerSource.value" + enabledValues: + - battery + - dc - id: battery version: 1 - id: refresh @@ -34,7 +50,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 900 - name: "parameter2" title: "2 Min. temperature change to report" @@ -42,7 +58,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 20 - name: "parameter3" title: "3 Min. humidity change to report" @@ -50,7 +66,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 255 + maximum: 255 default: 50 - name: "parameter4" title: "4 Enable led indication" @@ -96,7 +112,7 @@ preferences: preferenceType: integer definition: minimum: -10 - maximum : 10 + maximum: 10 default: 0 - name: "parameter23" title: "23 Low battery threshold" @@ -105,7 +121,7 @@ preferences: preferenceType: integer definition: minimum: 10 - maximum : 50 + maximum: 50 default: 20 - name: "parameter24" title: "24 Periodic Reports" @@ -114,7 +130,7 @@ preferences: preferenceType: integer definition: minimum: 0 - maximum : 2678400 + maximum: 2678400 default: 43200 - name: "parameter25" title: "25 Offset value for temperature" @@ -123,7 +139,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter26" title: "26 Offset value for Humidity" @@ -132,7 +148,7 @@ preferences: preferenceType: integer definition: minimum: -200 - maximum : 200 + maximum: 200 default: 0 - name: "parameter64" title: "64 Temperature Scale" diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-aerq-8/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-aerq-8/init.lua index 5e7de34e62..3b1ba2f5aa 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-aerq-8/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-aerq-8/init.lua @@ -19,8 +19,6 @@ local cc = require "st.zwave.CommandClass" local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) --- @type st.zwave.CommandClass.Battery local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) ---- @type st.zwave.CommandClass.SensorBinary -local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 2 }) --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) @@ -28,6 +26,8 @@ local log = require "log" local utils = require "st.utils" local MoldHealthConcern = capabilities.moldHealthConcern +local PowerSource = capabilities.powerSource +local TamperAlert = capabilities.tamperAlert local AEOTEC_AERQ_FINGERPRINTS = { { manufacturerId = 0x0371, productId = 0x0039 } -- Aeotec aerQ 8 EU/US/AU @@ -36,7 +36,7 @@ local AEOTEC_AERQ_FINGERPRINTS = { local function can_handle_aeotec_aerq(opts, driver, device, ...) for _, fingerprint in ipairs(AEOTEC_AERQ_FINGERPRINTS) do if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then - local subdriver = require("aeotec-aerq") + local subdriver = require("aeotec-aerq-8") return true, subdriver end end @@ -55,6 +55,10 @@ local function added_handler(driver, device) device:emit_event(PowerSource.powerSource.battery()) device:send(Battery:Get({})) + + device:register_native_capability_attr_handler("temperatureMeasurement", "temperature") + -- device:register_native_capability_attr_handler("colorControl", "hue") + end local function do_refresh(driver, device) @@ -69,7 +73,7 @@ local function notification_report_handler(self, device, cmd) if cmd.args.event == Notification.event.power_management.AC_MAINS_DISCONNECTED then event = capabilities.powerSource.powerSource.battery() elseif cmd.args.event == Notification.event.power_management.AC_MAINS_RE_CONNECTED then - event = capabilities.powerSource.powerSource.mains() + event = capabilities.powerSource.powerSource.dc() elseif cmd.args.event == Notification.event.power_management.POWER_HAS_BEEN_APPLIED then device:send(Battery:Get({})) end @@ -78,9 +82,18 @@ local function notification_report_handler(self, device, cmd) -- MOLD if cmd.args.notification_type == Notification.notification_type.WEATHER_ALARM then if cmd.args.event == Notification.event.weather_alarm.STATE_IDLE then - event = capabilities.moldHealthConcern.moldHealthConcern.good() + event = MoldHealthConcern.moldHealthConcern.good() elseif cmd.args.event == Notification.event.weather_alarm.MOISTURE_ALARM then - event = capabilities.moldHealthConcern.moldHealthConcern.moderate() + event = MoldHealthConcern.moldHealthConcern.moderate() + end + end + + -- TAMPER + if cmd.args.notification_type == Notification.notification_type.HOME_SECURITY then + if cmd.args.event == Notification.event.home_security.STATE_IDLE then + event = TamperAlert.tamper.clear() + elseif cmd.args.event == Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED then + event = TamperAlert.tamper.detected() end end diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua index d2a9e49cee..eed039555b 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua @@ -31,6 +31,9 @@ local ContactSensor = capabilities.contactSensor local PowerSource = capabilities.powerSource local ThreeAxis = capabilities.threeAxis local TamperAlert = capabilities.tamperAlert +local TemperatureMeasurement = capabilities.temperatureMeasurement +local RelativeHumidityMeasurement = capabilities.relativeHumidityMeasurement +local DewPoint = capabilities.dewPoint local AEOTEC_DOOR_WINDOW_SENSOR_8_FINGERPRINTS = { { manufacturerId = 0x0371, productId = 0x0037 } -- Aeotec Door Window Sensor 8 EU/US/AU @@ -105,10 +108,8 @@ local function notification_report_handler(self, device, cmd) -- TAMPER if cmd.args.notification_type == Notification.notification_type.HOME_SECURITY then if cmd.args.event == Notification.event.home_security.STATE_IDLE then - log.info("STATE_IDLE") event = TamperAlert.tamper.clear() elseif cmd.args.event == Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED then - log.info("TAMPERING_PRODUCT_COVER_REMOVED") event = TamperAlert.tamper.detected() end end @@ -118,7 +119,7 @@ local function notification_report_handler(self, device, cmd) end end -local function sensor_multilevel_report_handler(self, device, cmd) +local function sensor_multilevel_report_handler(self, device, cmd) local event local sensor_type = cmd.args.sensor_type local value = cmd.args.sensor_value @@ -129,26 +130,45 @@ local function sensor_multilevel_report_handler(self, device, cmd) local MIN_VAL = -10000 local MAX_VAL = 10000 - -- log.info(string.format("SensorMultilevel: type=%d, raw=%.1f", sensor_type, value)) value = math.max(MIN_VAL, math.min(MAX_VAL, value)) - -- log.info(string.format("Clamped: %.1f", value)) if (sensor_type == SensorMultilevel.sensor_type.ACCELERATION_X_AXIS) then x = value device:set_field("three_axis_x", x) - event = ThreeAxis.threeAxis(x, y, z) + event = ThreeAxis.threeAxis({value = {x, y, z}, unit = 'mG'}) elseif (sensor_type == SensorMultilevel.sensor_type.ACCELERATION_Y_AXIS) then y = value device:set_field("three_axis_y", y) - event = ThreeAxis.threeAxis(x, y, z) + event = ThreeAxis.threeAxis({value = {x, y, z}, unit = 'mG'}) elseif (sensor_type == SensorMultilevel.sensor_type.ACCELERATION_Z_AXIS) then z = value device:set_field("three_axis_z", z) - event = ThreeAxis.threeAxis(x, y, z) + event = ThreeAxis.threeAxis({value = {x, y, z}, unit = 'mG'}) + end + + if (sensor_type == SensorMultilevel.sensor_type.TEMPERATURE) then + local scale = 'C' + if (SensorMultilevel.scale.temperature.FAHRENHEIT == cmd.args.scale) then + scale = 'F' + end + event = TemperatureMeasurement.temperature({value = value, unit = scale}) + end + + if (sensor_type == SensorMultilevel.sensor_type.RELATIVE_HUMIDITY) then + event = RelativeHumidityMeasurement.humidity({value = value, unit = "%"}) + end + + if (sensor_type == SensorMultilevel.sensor_type.DEW_POINT) then + local scale = 'C' + if (SensorMultilevel.scale.dew_point.FAHRENHEIT == cmd.args.scale) then + scale = 'F' + end + event = DewPoint.dewpoint({value = value, unit = scale}) end if (event ~= nil) then device:emit_event(event) + return; end end @@ -161,9 +181,9 @@ local aeotec_door_window_sensor_8 = { [cc.NOTIFICATION] = { [Notification.REPORT] = notification_report_handler }, - -- [cc.SENSOR_MULTILEVEL] = { - -- [SensorMultilevel.REPORT] = sensor_multilevel_report_handler - -- } + [cc.SENSOR_MULTILEVEL] = { + [SensorMultilevel.REPORT] = sensor_multilevel_report_handler + } }, capability_handlers = { [capabilities.refresh.ID] = { diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor-8/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor-8/init.lua index d6ee9c0f1e..c121ad4961 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor-8/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor-8/init.lua @@ -115,9 +115,7 @@ end local function notification_report_handler(self, device, cmd) local active_profile = device:get_field("active_profile") local event - local event_parameter - log.info("event_parameter", utils.stringify_table(cmd.args.event_parameter)) if (0 ~= string.len(cmd.args.event_parameter)) then event_parameter = string.byte(cmd.args.event_parameter) @@ -128,6 +126,8 @@ local function notification_report_handler(self, device, cmd) -- TAMPER if cmd.args.event == Notification.event.home_security.STATE_IDLE and event_parameter == Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED then event = TamperAlert.tamper.clear() + elseif cmd.args.event == Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED and event_parameter == Notification.event.home_security.STATE_IDLE then + event = TamperAlert.tamper.detected() elseif active_profile == 'aeotec-water-sensor-8-motion' then -- MOTION if cmd.args.event == Notification.event.home_security.STATE_IDLE and event_parameter == Notification.event.home_security.MOTION_DETECTION then event = MotionSensor.motion.inactive() diff --git a/drivers/SmartThings/zwave-sensor/src/preferences.lua b/drivers/SmartThings/zwave-sensor/src/preferences.lua index 275656dc6a..e177486a87 100644 --- a/drivers/SmartThings/zwave-sensor/src/preferences.lua +++ b/drivers/SmartThings/zwave-sensor/src/preferences.lua @@ -197,7 +197,7 @@ local devices = { AEOTEC_DOOR_WINDOW_SENSOR_8 = { MATCHING_MATRIX = { mfrs = 0x0371, - product_types = {0x0000, 0x0001, 0x0002}, + product_types = {0x002, 0x0102, 0x0202}, product_ids = {0x0037} }, PARAMETERS = { @@ -222,7 +222,7 @@ local devices = { AEOTEC_WATER_SENSOR_8 = { MATCHING_MATRIX = { mfrs = 0x0371, - product_types = {0x0000, 0x0001, 0x0002}, + product_types = {0x0002, 0x0102, 0x0202}, product_ids = {0x0038} }, PARAMETERS = { diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_aerq_8.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_aerq_8.lua index 89acc99b54..2529cb290b 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_aerq_8.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_aerq_8.lua @@ -20,8 +20,8 @@ local zw_test_utils = require "integration_test.zwave_test_utils" local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) --- @type st.zwave.CommandClass.Battery local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) ---- @type st.zwave.CommandClass.SensorBinary -local SensorMultilevel = (require "st.zwave.CommandClass.SensorBinary")({ version = 2 }) +--- @type st.zwave.CommandClass.SensorMultilevel +local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ version = 11 }) --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) local t_utils = require "integration_test.utils" @@ -31,7 +31,7 @@ local sensor_endpoints = { command_classes = { {value = zw.BATTERY}, {value = zw.NOTIFICATION}, - {value = zw.SENSOR_BINARY}, + {value = zw.SENSOR_MULTILEVEL}, {value = zw.CONFIGURATION} } } @@ -202,6 +202,14 @@ test.register_message_test( direction = "send", message = mock_sensor:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' })) }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_sensor.id, capability_id = "temperatureMeasurement", capability_attr_id = "temperature" } + } + } } ) @@ -277,7 +285,7 @@ test.register_message_test( { channel = "capability", direction = "send", - message = mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) + message = mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.moderate()) } } ) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua index 1ccc9ec880..ac7c6e7730 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua @@ -84,6 +84,17 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Device init lifecycle event", + function() + test.socket.device_lifecycle:__queue_receive({ mock_sensor.id, "init" }) + + mock_sensor:set_field("three_axis_x", 0) + mock_sensor:set_field("three_axis_y", 0) + mock_sensor:set_field("three_axis_z", 0) + end +) + test.register_message_test( "Refresh should generate the correct commands", { @@ -109,289 +120,311 @@ test.register_message_test( } ) --- test.register_message_test( --- "Notification report STATE_IDLE event should be handled as tamperAlert clear", --- { --- { --- channel = "zwave", --- direction = "receive", --- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ --- notification_type = Notification.notification_type.HOME_SECURITY, --- event = Notification.event.home_security.STATE_IDLE, --- })) } --- }, --- { --- channel = "capability", --- direction = "send", --- message = mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) --- } --- } --- ) +test.register_message_test( + "Notification report STATE_IDLE event should be handled as tamperAlert clear", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.HOME_SECURITY, + event = Notification.event.home_security.STATE_IDLE, + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + } +) --- test.register_message_test( --- "Notification report TAMPERING_PRODUCT_COVER_REMOVED event should be handled as tamperAlert detected", --- { --- { --- channel = "zwave", --- direction = "receive", --- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ --- notification_type = Notification.notification_type.HOME_SECURITY, --- event = Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED, --- })) } --- }, --- { --- channel = "capability", --- direction = "send", --- message = mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) --- } --- } --- ) +test.register_message_test( + "Notification report TAMPERING_PRODUCT_COVER_REMOVED event should be handled as tamperAlert detected", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.HOME_SECURITY, + event = Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED, + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + } +) --- test.register_message_test( --- "Battery report should be handled", --- { --- { --- channel = "zwave", --- direction = "receive", --- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Battery:Report({ battery_level = 0x63 })) } --- }, --- { --- channel = "capability", --- direction = "send", --- message = mock_sensor:generate_test_message("main", capabilities.battery.battery(99)) --- } --- } --- ) +test.register_message_test( + "Battery report should be handled", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Battery:Report({ battery_level = 0x63 })) } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.battery.battery(99)) + } + } +) --- test.register_message_test( --- "Notification report AC_MAINS_DISCONNECTED event should be handled power source state battery", --- { --- { --- channel = "zwave", --- direction = "receive", --- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ --- notification_type = Notification.notification_type.POWER_MANAGEMENT, --- event = Notification.event.power_management.AC_MAINS_DISCONNECTED, --- })) } --- }, --- { --- channel = "capability", --- direction = "send", --- message = mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.battery()) --- } --- } --- ) +test.register_message_test( + "Notification report AC_MAINS_DISCONNECTED event should be handled power source state battery", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.AC_MAINS_DISCONNECTED, + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.battery()) + } + } +) --- test.register_message_test( --- "Notification report AC_MAINS_RE_CONNECTED event should be handled power source state dc", --- { --- { --- channel = "zwave", --- direction = "receive", --- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ --- notification_type = Notification.notification_type.POWER_MANAGEMENT, --- event = Notification.event.power_management.AC_MAINS_RE_CONNECTED, --- })) } --- }, --- { --- channel = "capability", --- direction = "send", --- message = mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.dc()) --- } --- } --- ) +test.register_message_test( + "Notification report AC_MAINS_RE_CONNECTED event should be handled power source state dc", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.AC_MAINS_RE_CONNECTED, + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.dc()) + } + } +) --- test.register_message_test( --- "Notification report POWER_HAS_BEEN_APPLIED event should be send battery get", --- { --- { --- channel = "zwave", --- direction = "receive", --- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ --- notification_type = Notification.notification_type.POWER_MANAGEMENT, --- event = Notification.event.power_management.POWER_HAS_BEEN_APPLIED, --- })) } --- }, --- { --- channel = "zwave", --- direction = "send", --- message = zw_test_utils.zwave_test_build_send_command( --- mock_sensor, --- Battery:Get({}) --- ) --- } --- } --- ) +test.register_message_test( + "Notification report POWER_HAS_BEEN_APPLIED event should be send battery get", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.POWER_MANAGEMENT, + event = Notification.event.power_management.POWER_HAS_BEEN_APPLIED, + })) } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_sensor, + Battery:Get({}) + ) + } + } +) --- test.register_message_test( --- "Notification report WINDOW_DOOR_IS_OPEN event should be handled contact sensor state open", --- { --- { --- channel = "zwave", --- direction = "receive", --- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ --- notification_type = Notification.notification_type.ACCESS_CONTROL, --- event = Notification.event.access_control.WINDOW_DOOR_IS_OPEN, --- })) } --- }, --- { --- channel = "capability", --- direction = "send", --- message = mock_sensor:generate_test_message("main", capabilities.contactSensor.contact.open()) --- } --- } --- ) +test.register_message_test( + "Notification report WINDOW_DOOR_IS_OPEN event should be handled contact sensor state open", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = Notification.event.access_control.WINDOW_DOOR_IS_OPEN, + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.contactSensor.contact.open()) + } + } +) --- test.register_message_test( --- "Notification report WINDOW_DOOR_IS_CLOSED event should be handled contact sensor state closed", --- { --- { --- channel = "zwave", --- direction = "receive", --- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ --- notification_type = Notification.notification_type.ACCESS_CONTROL, --- event = Notification.event.access_control.WINDOW_DOOR_IS_CLOSED, --- })) } --- }, --- { --- channel = "capability", --- direction = "send", --- message = mock_sensor:generate_test_message("main", capabilities.contactSensor.contact.closed()) --- } --- } --- ) +test.register_message_test( + "Notification report WINDOW_DOOR_IS_CLOSED event should be handled contact sensor state closed", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.ACCESS_CONTROL, + event = Notification.event.access_control.WINDOW_DOOR_IS_CLOSED, + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.contactSensor.contact.closed()) + } + } +) --- test.register_message_test( --- "Temperature reports should be handled", --- { --- { --- channel = "zwave", --- direction = "receive", --- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ --- sensor_type = SensorMultilevel.sensor_type.TEMPERATURE, --- scale = SensorMultilevel.scale.temperature.CELSIUS, --- sensor_value = 21.5 })) --- } --- }, --- { --- channel = "capability", --- direction = "send", --- message = mock_sensor:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' })) --- }, --- } --- ) +test.register_message_test( + "Temperature reports should be handled", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ + sensor_type = SensorMultilevel.sensor_type.TEMPERATURE, + scale = SensorMultilevel.scale.temperature.CELSIUS, + sensor_value = 21.5 })) + } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' })) + }, + } +) --- test.register_message_test( --- "Humidity reports should be handled", --- { --- { --- channel = "zwave", --- direction = "receive", --- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ --- sensor_type = SensorMultilevel.sensor_type.RELATIVE_HUMIDITY, --- sensor_value = 70 })) --- } --- }, --- { --- channel = "capability", --- direction = "send", --- message = mock_sensor:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 70, })) --- }, --- } --- ) +test.register_message_test( + "Humidity reports should be handled", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ + sensor_type = SensorMultilevel.sensor_type.RELATIVE_HUMIDITY, + scale = SensorMultilevel.scale.relative_humidity.PERCENTAGE, + sensor_value = 70 })) + } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 70, unit= '%' })) + }, + } +) --- test.register_message_test( --- "Sensor multilevel reports dew_point type command should be handled as dew point measurement", --- { --- { --- channel = "zwave", --- direction = "receive", --- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ --- sensor_type = SensorMultilevel.sensor_type.DEW_POINT, --- sensor_value = 8, --- scale = 0 --- })) } --- }, --- { --- channel = "capability", --- direction = "send", --- message = mock_sensor:generate_test_message("main", capabilities.dewPoint.dewpoint({value = 8, unit = "C"})) --- } --- } --- ) +test.register_message_test( + "Sensor multilevel reports dew_point type command should be handled as dew point measurement", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ + sensor_type = SensorMultilevel.sensor_type.DEW_POINT, + sensor_value = 8, + scale = 0 + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.dewPoint.dewpoint({value = 8, unit = "C"})) + } + } +) --- test.register_coroutine_test( --- "Three Axis reports should be correctly handled", --- function() --- test.socket.zwave:__queue_receive({ --- mock_sensor.id, --- SensorMultilevel:Report({ --- sensor_type = SensorMultilevel.sensor_type.ACCELERATION_X_AXIS, --- sensor_value = 1.962, --- scale = SensorMultilevel.scale.acceleration_x_axis.METERS_PER_SQUARE_SECOND } --- ) --- }) --- test.socket.zwave:__queue_receive({ --- mock_sensor.id, --- SensorMultilevel:Report({ --- sensor_type = SensorMultilevel.sensor_type.ACCELERATION_Y_AXIS, --- sensor_value = 1.962, --- scale = SensorMultilevel.scale.acceleration_y_axis.METERS_PER_SQUARE_SECOND } --- ) --- }) --- test.socket.zwave:__queue_receive({ --- mock_sensor.id, --- SensorMultilevel:Report({ --- sensor_type = SensorMultilevel.sensor_type.ACCELERATION_Z_AXIS, --- sensor_value = 3.924, --- scale = SensorMultilevel.scale.acceleration_z_axis.METERS_PER_SQUARE_SECOND } --- ) --- }) --- test.socket.capability:__expect_send( --- mock_sensor:generate_test_message("main", --- capabilities.threeAxis.threeAxis({value = {200, 200, 400}, unit = 'mG'}) --- ) --- ) --- end --- ) +test.register_coroutine_test( + "Three Axis x reports should be correctly handled", + function() + test.socket.zwave:__queue_receive({ + mock_sensor.id, + SensorMultilevel:Report({ + sensor_type = SensorMultilevel.sensor_type.ACCELERATION_X_AXIS, + sensor_value = 200, + scale = SensorMultilevel.scale.acceleration_x_axis.METERS_PER_SQUARE_SECOND } + ) + }) + test.socket.capability:__expect_send( + mock_sensor:generate_test_message("main", + capabilities.threeAxis.threeAxis({value = {200, 0, 0}, unit = 'mG'}) + ) + ) + end +) +test.register_coroutine_test( + "Three Axis y reports should be correctly handled", + function() + test.socket.zwave:__queue_receive({ + mock_sensor.id, + SensorMultilevel:Report({ + sensor_type = SensorMultilevel.sensor_type.ACCELERATION_Y_AXIS, + sensor_value = 200, + scale = SensorMultilevel.scale.acceleration_y_axis.METERS_PER_SQUARE_SECOND } + ) + }) + test.socket.capability:__expect_send( + mock_sensor:generate_test_message("main", + capabilities.threeAxis.threeAxis({value = {0, 200, 0}, unit = 'mG'}) + ) + ) + end +) --- test.register_message_test( --- "Notification report type WEATHER_ALARM event STATE_IDLE should be handled mold healt concern state good", --- { --- { --- channel = "zwave", --- direction = "receive", --- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ --- notification_type = Notification.notification_type.WEATHER_ALARM, --- event = Notification.event.weather_alarm.STATE_IDLE, --- }))} --- }, --- { --- channel = "capability", --- direction = "send", --- message = mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) --- } --- } --- ) +test.register_coroutine_test( + "Three Axis z reports should be correctly handled", + function() + test.socket.zwave:__queue_receive({ + mock_sensor.id, + SensorMultilevel:Report({ + sensor_type = SensorMultilevel.sensor_type.ACCELERATION_Z_AXIS, + sensor_value = 400, + scale = SensorMultilevel.scale.acceleration_z_axis.METERS_PER_SQUARE_SECOND } + ) + }) + test.socket.capability:__expect_send( + mock_sensor:generate_test_message("main", + capabilities.threeAxis.threeAxis({value = {0, 0, 400}, unit = 'mG'}) + ) + ) + end +) --- test.register_message_test( --- "Notification report type WEATHER_ALARM event MOISTURE_ALARM should be handled mold healt concern state moderate", --- { --- { --- channel = "zwave", --- direction = "receive", --- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ --- notification_type = Notification.notification_type.WEATHER_ALARM, --- event = Notification.event.weather_alarm.MOISTURE_ALARM, --- }))} --- }, --- { --- channel = "capability", --- direction = "send", --- message = mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) --- } --- } --- ) +test.register_message_test( + "Notification report type WEATHER_ALARM event STATE_IDLE should be handled mold healt concern state good", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.WEATHER_ALARM, + event = Notification.event.weather_alarm.STATE_IDLE, + }))} + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) + } + } +) + +test.register_message_test( + "Notification report type WEATHER_ALARM event MOISTURE_ALARM should be handled mold healt concern state moderate", + { + { + channel = "zwave", + direction = "receive", + message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.WEATHER_ALARM, + event = Notification.event.weather_alarm.MOISTURE_ALARM, + }))} + }, + { + channel = "capability", + direction = "send", + message = mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.moderate()) + } + } +) test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_8.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_8.lua index 04d767d0c6..53122c6389 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_8.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_8.lua @@ -230,16 +230,12 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_water_sensor:generate_test_message("main", capabilities.moldHealthConcern.supportedMoldValues({"good", "moderate"})) ) - test.socket.capability:__expect_send( mock_water_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) ) - test.socket.capability:__expect_send( mock_water_sensor:generate_test_message("main", capabilities.powerSource.powerSource.battery()) ) - - test.socket.zwave:__expect_send( zw_test_utils.zwave_test_build_send_command( mock_water_sensor, @@ -294,24 +290,24 @@ test.register_message_test( } ) -test.register_coroutine_test( - "Notification report TAMPERING_PRODUCT_COVER_REMOVED event should be handled as tamperAlert detected", - function() - test.timer.__create_and_queue_test_time_advance_timer(10, "oneshot") - test.socket.zwave:__queue_receive( - { - mock_water_sensor.id, - zw_test_utils.zwave_test_build_receive_command( - Notification:Report( - { - notification_type = Notification.notification_type.HOME_SECURITY, - event = Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED - }) - ) - } - ) - test.socket.capability:__expect_send(mock_water_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.detected())) - end +test.register_message_test( + "Notification report STATE_IDLE event should be handled tamper alert state clear", + { + { + channel = "zwave", + direction = "receive", + message = { mock_water_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ + notification_type = Notification.notification_type.HOME_SECURITY, + event = Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED, + event_parameter = string.char(Notification.event.home_security.STATE_IDLE) + })) } + }, + { + channel = "capability", + direction = "send", + message = mock_water_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + } ) test.register_message_test( @@ -428,7 +424,6 @@ test.register_message_test( } ) - for param_value, data in pairs(DEVICE_PROFILES) do local value = param_value local profile = data.profile diff --git a/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua index 491960ed26..cfa5c3ee76 100644 --- a/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/timed-tamper-clear/init.lua @@ -24,7 +24,19 @@ local TAMPER_CLEAR = 10 local excluded_devices = { FIBARO_DOOR_WINDOW = { mfrs = 0x010F - } + }, + AEOTEC_AERQ_8 = { + mfrs = 0x0371, + product_ids = 0x0018 + }, + AEOTEC_DOOR_WINDOW_SENSOR_8 = { + mfrs = 0x0371, + product_ids = 0x0037 + }, + AEOTEC_WATER_SENSOR_8 = { + mfrs = 0x0371, + product_ids = 0x0038 + }, } local function can_handle_tamper_event(opts, driver, zw_device, cmd, ...) From 393dfce78ec06d2b0c59e1f66cfdf18d5c742ee9 Mon Sep 17 00:00:00 2001 From: mh-zwave Date: Mon, 30 Mar 2026 15:36:11 +0200 Subject: [PATCH 08/10] Update preferences.lua --- drivers/SmartThings/zwave-sensor/src/preferences.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/zwave-sensor/src/preferences.lua b/drivers/SmartThings/zwave-sensor/src/preferences.lua index e177486a87..b45b73e089 100644 --- a/drivers/SmartThings/zwave-sensor/src/preferences.lua +++ b/drivers/SmartThings/zwave-sensor/src/preferences.lua @@ -197,7 +197,7 @@ local devices = { AEOTEC_DOOR_WINDOW_SENSOR_8 = { MATCHING_MATRIX = { mfrs = 0x0371, - product_types = {0x002, 0x0102, 0x0202}, + product_types = {0x0002, 0x0102, 0x0202}, product_ids = {0x0037} }, PARAMETERS = { From 1e572f5555f343bc0a326cbecabf7f6cc711e5c5 Mon Sep 17 00:00:00 2001 From: mh-zwave Date: Mon, 30 Mar 2026 16:03:24 +0200 Subject: [PATCH 09/10] Update fingerprints.yml --- .../SmartThings/zwave-sensor/fingerprints.yml | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/drivers/SmartThings/zwave-sensor/fingerprints.yml b/drivers/SmartThings/zwave-sensor/fingerprints.yml index e879cc1270..0108675316 100644 --- a/drivers/SmartThings/zwave-sensor/fingerprints.yml +++ b/drivers/SmartThings/zwave-sensor/fingerprints.yml @@ -548,19 +548,58 @@ zwaveManufacturer: productType: 0x0100 productId: 0x0082 deviceProfileName: shelly-wave-motion - - id: "aeotec/contact/8" + - id: "aeotec/contact/8/eu" deviceLabel: Aeotec Door Window Sensor 8 manufacturerId: 0x0371 + productType: 0x0002 productId: 0x0037 deviceProfileName: aeotec-door-window-sensor-8 - - id: aeotec/aerq/8 + - id: "aeotec/contact/8/us" + deviceLabel: Aeotec Door Window Sensor 8 + manufacturerId: 0x0371 + productType: 0x0102 + productId: 0x0037 + deviceProfileName: aeotec-door-window-sensor-8 + - id: "aeotec/contact/8/au" + deviceLabel: Aeotec Door Window Sensor 8 + manufacturerId: 0x0371 + productType: 0x0202 + productId: 0x0037 + deviceProfileName: aeotec-door-window-sensor-8 + - id: aeotec/aerq/8/eu + deviceLabel: Aeotec aerQ 8 + manufacturerId: 0x0371 + productType: 0x0002 + productId: 0x0039 + deviceProfileName: aeotec-aerq-8 + - id: aeotec/aerq/8/us deviceLabel: Aeotec aerQ 8 manufacturerId: 0x0371 + productType: 0x0102 productId: 0x0039 deviceProfileName: aeotec-aerq-8 - - id: "aeotec/water/8" + - id: aeotec/aerq/8/au + deviceLabel: Aeotec aerQ 8 + manufacturerId: 0x0371 + productType: 0x0202 + productId: 0x0039 + deviceProfileName: aeotec-aerq-8 + - id: "aeotec/water/8/eu" + deviceLabel: Aeotec Water Sensor 8 + manufacturerId: 0x0371 + productType: 0x0002 + productId: 0x0038 + deviceProfileName: aeotec-water-sensor-8 + - id: "aeotec/water/8/us" + deviceLabel: Aeotec Water Sensor 8 + manufacturerId: 0x0371 + productType: 0x0102 + productId: 0x0038 + deviceProfileName: aeotec-water-sensor-8 + - id: "aeotec/water/8/au" deviceLabel: Aeotec Water Sensor 8 manufacturerId: 0x0371 + productType: 0x0202 productId: 0x0038 deviceProfileName: aeotec-water-sensor-8 zwaveGeneric: From 1dd72a55a29853a98112937101b9c49f4c21e662 Mon Sep 17 00:00:00 2001 From: mh-zwave Date: Tue, 31 Mar 2026 11:11:43 +0200 Subject: [PATCH 10/10] cleanup --- .../SmartThings/zwave-sensor/src/aeotec-aerq-8/init.lua | 7 ++----- .../zwave-sensor/src/aeotec-door-window-sensor-8/init.lua | 8 +++----- .../zwave-sensor/src/aeotec-water-sensor-8/init.lua | 8 +------- .../src/test/test_aeotec_door_window_sesnor_8.lua | 2 +- .../zwave-sensor/src/test/test_aeotec_water_sensor_8.lua | 2 +- 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-aerq-8/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-aerq-8/init.lua index 3b1ba2f5aa..b523b01c97 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-aerq-8/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-aerq-8/init.lua @@ -22,9 +22,6 @@ local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) -local log = require "log" -local utils = require "st.utils" - local MoldHealthConcern = capabilities.moldHealthConcern local PowerSource = capabilities.powerSource local TamperAlert = capabilities.tamperAlert @@ -47,13 +44,13 @@ local function added_handler(driver, device) device:send(Configuration:Get({ parameter_number = 10 })) device:emit_event(MoldHealthConcern.supportedMoldValues({"good", "moderate"})) - + -- Default value device:emit_event(MoldHealthConcern.moldHealthConcern.good()) -- Default value device:emit_event(PowerSource.powerSource.battery()) - + device:send(Battery:Get({})) device:register_native_capability_attr_handler("temperatureMeasurement", "temperature") diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua index eed039555b..9ca600d55f 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua @@ -24,8 +24,6 @@ local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ ve --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) -local log = require "log" - local MoldHealthConcern = capabilities.moldHealthConcern local ContactSensor = capabilities.contactSensor local PowerSource = capabilities.powerSource @@ -53,13 +51,13 @@ local function added_handler(driver, device) device:send(Configuration:Get({ parameter_number = 10 })) device:emit_event(MoldHealthConcern.supportedMoldValues({"good", "moderate"})) - + -- Default value device:emit_event(MoldHealthConcern.moldHealthConcern.good()) -- Default value device:emit_event(PowerSource.powerSource.battery()) - + device:send(Battery:Get({})) end @@ -123,7 +121,7 @@ local function sensor_multilevel_report_handler(self, device, cmd) local event local sensor_type = cmd.args.sensor_type local value = cmd.args.sensor_value - + local x = device:get_field("three_axis_x") or 0 local y = device:get_field("three_axis_y") or 0 local z = device:get_field("three_axis_z") or 0 diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor-8/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor-8/init.lua index c121ad4961..6fa0d3b637 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor-8/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-water-sensor-8/init.lua @@ -22,9 +22,6 @@ local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) -local log = require "log" -local utils = require "st.utils" - local MoldHealthConcern = capabilities.moldHealthConcern local CarbonDioxideHealthConcern = capabilities.carbonDioxideHealthConcern local SoundDetection = capabilities.soundDetection @@ -41,7 +38,7 @@ local AEOTEC_WATER_SENSOR_8_FINGERPRINTS = { { manufacturerId = 0x0371, productId = 0x0038 } -- Aeotec Water Sensor 8 EU/US/AU } -DEVICE_PROFILES = { +local DEVICE_PROFILES = { [0] = { profile = "aeotec-water-sensor-8"}, [1] = { profile = "aeotec-water-sensor-8-smoke"}, [2] = { profile = "aeotec-water-sensor-8-co"}, @@ -66,8 +63,6 @@ end local function set_profile(device, profile) local current = device:get_field("active_profile") if current ~= profile.profile then - log.info(string.format("Switching profile to: %s", profile.profile)) - device:try_update_metadata({ profile = profile.profile }) device:set_field("active_profile", profile.profile) @@ -224,7 +219,6 @@ end local function configuration_report_handler(self, device, cmd) local param_number = cmd.args.parameter_number local value = cmd.args.configuration_value - log.info(string.format("Received Configuration Report #%d = %d", param_number, value)) if param_number == 10 then local mapping = DEVICE_PROFILES[value] diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua index ac7c6e7730..44ae9f52ae 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua @@ -88,7 +88,7 @@ test.register_coroutine_test( "Device init lifecycle event", function() test.socket.device_lifecycle:__queue_receive({ mock_sensor.id, "init" }) - + mock_sensor:set_field("three_axis_x", 0) mock_sensor:set_field("three_axis_y", 0) mock_sensor:set_field("three_axis_z", 0) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_8.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_8.lua index 53122c6389..ca6aeb8169 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_8.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_water_sensor_8.lua @@ -99,7 +99,7 @@ local mock_smoke_sensor = test.mock_device.build_test_zwave_device({ zwave_product_id = 0x0038, }) -DEVICE_PROFILES = { +local DEVICE_PROFILES = { [0] = { profile = "aeotec-water-sensor-8", mock_device = mock_water_sensor,